Welcome to my REST demo. This is an educational exercise for myself but maybe it could help someone else on the same journey. I will start with some background and then gradually introduce some working examples with code.
So, what is REST? Rest is REpresentational State Transfer. It's safe to say that without elaboration, that does not tell us much. However, we can elaborate by considering the 6 principles of REST. Below, I will discuss these principles and how we can apply them to Node.js and Express as well as I can from my understanding so far.
This can be described crudely as keeping the client side code separate from the Server side. By using the http GET, PUT, POST and DELETE methods to communicate with the server and control the transmission of data both ways, we have a standardised format which we can adhere to. If we have to make changes to either the server side or the client side software, the abstraction provided by this protocol means we can do so with low risk of causing problems with the other.
This simply means that when a request is made, it will not be denied because of some state set up elsewhere, we do nott check for a connection or an authentication before we submit, everything is done within the request, every request. This goes a long way towards preserving the abstraction we mentioned above.
Browsers already have the ability to cache data built in, sometimes to the frustration of the developer. Think how ofter you have to do a forced refresh to view a small CSS change etc. However, it's an important and useful feature. With libraries like the NodeCache available, we can simply apply this technology to a Node website which will lead to a faster and more efficient browser experienece for a user interacting with our data.
This is regardingt the interface used to communicate. In our case the GET, PUT, POST and DELETE responses we send and the result we use to return the data. It's a standardised system we can use.
The individual components of the system do not need to understand each other, we can work on one module without worrying about the other. Once again, abstraction is the goal. I see strong parallels here to OOP. In our case, we have the requests from the browser, the routing is separate in Express and if we code effectively, we use separate modules of code to connect our routes to the database.
This is the facility for the client to download code to increase the functtionality. The best example I can think of right now is perhaps the ability to download an app. Particularily on a mobile device, this would improve the user experience as it would be more customised to the device than a responsive site. I'm not sure how good an example this is though.
Now we can begin with some practical. To start with, I have a simple form below which sends data to GET, POST, PUT and DELETE. The form uses the Fetch() api within JavaScript on the client side to send requests to the server at a specific route.
For now, within the routing, we just send the data right back and it appears in the window below with some text added to confirm it came back from the correct path:
//server side scripts handling the responses
//handle to where we will display the output from the server
const responseContainer = document.getElementById("responseContent");
//input box handles (to access client data)
const getInputBox = document.getElementById('getInputBox');
const postInputBox = document.getElementById('postInputBox');
const putInputBox = document.getElementById('putInputBox');
const deleteInputBox = document.getElementById('deleteInputBox');
//button handles
const getButton = document.getElementById("get");
const postButton = document.getElementById("post");
const putButton = document.getElementById("put");
const deleteButton = document.getElementById("delete");
//button listeners
getButton.addEventListener("click",doGet);
postButton.addEventListener("click",doPost);
putButton.addEventListener("click",doPut);
deleteButton.addEventListener("click",doDelete);
//Functions tied to our listeners (what gets done when buttons are pushed)
function doGet(){
//build a url with the box content as a query
let urlWithQuery = "/data?data=" + getInputBox.value
fetch(urlWithQuery)
.then(response => response.text())
.then(text => responseContainer.innerHTML+=(text + "
"));
}
function doPost(){
//send the request
fetch("/data", buildRequest('POST', postInputBox.value))
.then(response => response.text())
.then(text => responseContainer.innerHTML+=(text + "
"));
}
function doPut(){
fetch("/data", buildRequest('PUT', putInputBox.value))
.then(response => response.text())
.then(text => responseContainer.innerHTML+=(text + "
"));
}
function doDelete(){
fetch("/data", buildRequest('DELETE', deleteInputBox.value))
.then(response => response.text())
.then(text => responseContainer.innerHTML+=(text + "
"));
}
//request building function,
function buildRequest(requestType, requestMessage){
const requestData = {
method: requestType,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({data: requestMessage}) //We have to use stringify else it will try and parse the JSON twice
};
return requestData;
}
We don't reload, the buttons are monitored by listeners. When pressed, the Fetch() api is used by calling the appropriate function (doGet(), doPost, doPut() & doDelete()).
These functions call buildRequest() which returns a JSON header which we can send to the server with the appropriate method (GET, PUT, POST or DELETE) embedded within a JSON. When recieved by EXPRESS it's routed accordingly as we will see shortly. Note, the data we are sending is embedded ino the body on everywhere but the GET method where it is sent in a query instead. More about that later.
My node app has rather too much going on to list the whole thing here because it is serving a number of 'mini' websites. Therefore, I am just going the show the code needed here. First we have the app.js file, the main node app itself:
//express modules
const express = require('express');
//my modules
const routes = require('./routes');
const app = express();
const port = process.env.PORT ||3000;
//pug pages
app.set('view engine', 'pug');
app.locals.pretty = true; //format rendered html neatly
app.use(routes.pugRoutes);
//our REST routes
routes.restRoutes(app, express);
// static pages using just express
app.use(express.static('public'));
//lets handle our error pages
app.use( (req, res) => {
res.redirect('/404.html')
});
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});
As you can see, we use the express module. This was imported using npm earlier. The other requirement is a routing file where I have separated out my routes into separate functions. This makes things modular, a lot more easy to manage. It helps us comply with the requirements of a REST api. Essentially, it's where i keep my own middleware functions.
The following lines set up our rendering engine. In this case I am using PUG (formerly known as JADE):
app.set('view engine', 'pug');
app.locals.pretty = true; //format rendered html neatly
app.use(routes.pugRoutes);
The routes.restRoutes(app, express);
calls my middleware function to sort the REST routes, we will see a bit later. You can see I have a route at the bottom which will default if nothing else is caught. This redirects to my custom 404 page. The last couple of lines of code set up Node to listen to a port, that completes the server. Next, we have my routing function within my routes.js file:
//Routes for my REST demo
function restRoutes(app, express){
app.use(express.json()); //without this, Express won't import body at all!
//Review (Has to be query, no body!)
app.get('/data', (req, res) => {
res.send("Getting (Review): " + req.query.data);
});
//Create (All the rest, we can look at our body)
app.post('/data', (req, res) => {
res.send("Posting (Create): " + req.body.data);
});
//Update
app.put('/data', (req, res) => {
res.send("Putting (Update): " + req.body.data);
});
//Delete
app.delete('/data', (req, res) => {
res.send("Deleting: " + req.body.data);
});
}
For the GET request, the input box text is sent via a query in the url. This is because GET does not have a body within which to sent anything to the server. For PUT, POST and DELETE responses, the body can be used to send the data in a name - value pair.