An Express application is most often used as a backend application in a client-server architecture whereas the client could be written in React.js or another popular frontend solution and the server could be written in Express. Both entities result in a client-server architecture (frontend and backend relationship) whereas the backend would be needed for (A) business logic that shouldnât be exposed as source code to the frontend application â otherwise it would be accessible in the browser â or for (B) establishing connections to third-party data sources (e.g. database(s)).
However, donât mistake client application always for frontend and server application always for backend here. These terms cannot be exchanged that easily. Whereas a frontend application is usually something seen in the browser, a backend usually performs business logic that shouldnât be exposed in a browser and often connects to a database as well.
But, in contrast, the terms client and server are a matter of perspective. A backend application (Backend 1) which consumes another backend application (Backend 2) becomes a client application (Backend 1) for the server application (Backend 2). However, the same backend application (Backend 1) is still the server for another client application which is the frontend application (Frontend).
If you want to answer the client-server question if someone asks you what role an entity plays in a client-server architecture, always ask yourself who (server) is serving whom (client) and who (client) consumes whomâs (backend) functionalities?
Thatâs the theory behind client-server architectures and how to relate to them. Letâs get more practical again. How do client and server applications communicate with each other? Over the years, there existed a few popular communication interfaces ( APIs ) between both entities. However, the most popular one is called REST defined in 2000 by Roy Fielding. Itâs an architecture that leverages the HTTP protocol to enable communication between a client and a server application. A server application that offers a REST API is also called a RESTful server. Servers that donât follow the REST architecture a 100% are rather called RESTish than RESTful. In the following, we are going to implement such REST API for our Express server application, but first letâs get to know the tooling that enables us to interact with a REST API.
If you havenât heard about cURL, this section gives you a short excursus about whatâs cURL and how to use it to interact with (REST) APIs. The definition taken from Wikipedia says: âcURL [â¦] is a computer software project providing a library and command-line tool for transferring data using various protocols.â Since REST is an architecture that uses HTTP, a server that exposes a RESTful API can be consumed with cURL, because HTTP is one of the various protocols.
First, letâs install it on the command line. For now, the installation guide is for MacOS users, but I guess by looking up âcurl for windowsâ online, you will find the setup guide for your desired OS (e.g. Windows) too. In this guide, we will use Homebrew to install it. If you donât have Homebrew, install it with the following command on the command line:
If you havenât heard about Homebrew, read more about it over here . Next, install cURL with Homebrew:
Now, start your Express server from the previous sections. Once your application is started, execute curl http://localhost:3000 in another command line window. Make sure the port matches your port and the Express server is running. After executing the command, you should see the âHello World!â printed on the command line. Congratulations, you just have consumed your Express server as a client with something else than a browser.
Whether you access your Express application on http://localhost:3000 in the browser or via the command line with cURL, you should see the same result. Both tools act as clients whereas the Express application is your server. You will see in the next sections how to use cURL to verify your Express applicationâs REST API, that we are going to implement together, on the command line instead of in the browser.
Express is a perfect choice for a server when it comes to creating and exposing APIs (e.g. REST API) to communicate as a client with your server application. Previously you have already implemented one Express route, which sends a âHello World!â, that you have accessed via the browser and cURL. Letâs set up more routes to accommodate a RESTful API for your Express application eventually. Add the following routes to your Express application whereas the URI itself doesnât change, but the method used from your Express instance:
Every Express instanceâs method maps to a HTTP method. Letâs see how this works: Start your Express server on the command line again, if it isnât running already, and execute four cURL commands in another command line window. You should see the following output for the commands:
By default cURL will use a HTTP GET method. However, you can specify the HTTP method with the -X flag (or --request flag). Depending on the HTTP method you are choosing, you will access different routes of your Express application â which here represent only a single API endpoint with an URI so far. You will see later other additions that you can add to your cURL requests.
Thatâs one of the key aspects of REST: It uses HTTP methods to perform operations on URI(s). Often these operations are referred to as CRUD operations for create, read, update, and delete operations. Next you will see on what these operations are used on the URIs (resources).
Another important aspect of REST is that every URI acts as a resource. So far, you have only operated on the root URI with your CRUD operations, which doesnât really represent a resource in REST. In contrast, a resource could be a user resource, for example. Change your previously introduced routes to the following:
With cURL on your command line, you can go through the resource â represented by one URI http://localhost:3000/users â which offers all the CRUD operations via HTTP methods:
You will see a similar output as before, but this time you are operating on a user resource. For example, if you want to create a user, you hit the following URI:
Obviously we donât transfer any information for creating a user yet, however, the API endpoint for creating a user would be available now. One piece is missing to make the PUT HTTP method (update operation) and DELETE HTTP method (delete operation) RESTful from a URIâs point of view:
In order to delete or update a user resource, you would need to know the exact user. Thatâs where unique identifiers are used. In our Express routes, we can assign unique identifiers with parameters in the URI. Then the callback function holds the URIâs parameter in the request objectâs properties. Try again a cURL operation on /users/1 , /users/2 or another identifier with a DELETE or UPDATE HTTP method and verify that the identifier shows up in the command line as output.
You may be still wondering: What value brings the combination of URIs and HTTP methods â which make up the majority of the REST philosophy â to my application?
Letâs imagine we wouldnât just return a result, as we do at the moment, but would act properly on the received operation instead. For instance, the Express server could be connected to a database that stores user entities in a user table. Now, when consuming the REST API as a client (e.g. cURL, browser, or also a React.js application ), you could retrieve all users from the database with a HTTP GET method on the /users URI or, on the same resource, create a new user with a HTTP POST method.
Suddenly you would be able to read and write data from and to a database from a client application. Everything that makes it possible is a backend application which enables you to write a interface (e.g. REST API) for CRUD operations:
Whereas itâs important to notice that the REST API belongs to the server application:
You can take this always one step further by having multiple server applications offering REST APIs. Often they come with the name microservices or web services whereas each server application offers a well-encapsulated functionality. The servers even donât have to use the same programming language, because they are communicating over a programming language agnostic interface (HTTP with REST). Although the interfaces (APIs) donât have to be necessarily REST APIs.
Letâs take everything we learned in theory, so far, one step further towards a real application by sending real data across the wire. The data will be sample data, which will not come from a database yet, but will be hardcoded in the source code instead:
Next to the user entities, we will have message entities too. Both entities are related to each other by providing the necessary information as identifiers (e.g. a message has a message creator). Thatâs how a message is associated with a user and how you would retrieve the data from a database, too, whereas each entity (user, message) has a dedicated database table. Both are represented as objects that can be accessed by identifiers.
Letâs start by providing two routes for reading the whole list of users and a single user by identifier:
Whereas we pick a user from the object by identifier for the single users route, we transform the user object to a list of users for the all users route. The same should be possible for the message resource:
Try all four routes with cURL on the command line yourself. Thatâs only about reading data. Next, we will discuss the other CRUD operations to create, update and delete resources to actually write data. However, we will not get around a custom Express middleware and a Express middleware provided by the Express ecosystem. Thatâs why we will discuss the subject of the Express middleware next while implementing the missing CRUD operations.
Before we jump into Express middleware again, letâs see how a scenario for creating a message could be implemented in our Express application. Since we are creating a message without a database ourselves, we need a helper library to generate unique identifiers for us. Install this helper library on the command line:
Next import it at the top of your src/index.js file:
Now, create a message with a new route that uses a HTTP POST method:
We generate a unique identifier for the message with the new library, use it as property in a message object with a shorthand object property initialization , assign the message by identifier in the messages object â which is our pseudo database â, and return the new message after it has been created.
However, something is missing for the message. In order to create a message, a client has to provide the text string for the message. Fortunately a HTTP POST method makes it possible to send data as payload in a body. Thatâs why we can use the incoming request ( req ) to extract a payload from it:
Accessing the payload of an HTTP POST request is provided within Express with its built-in middleware which is based on body-parser . It enables us to transform body types from our request object (e.g. json, urlencoded):
This extracts the entire body portion of an incoming request stream and makes it accessible on req.body . Now the body with the messageâs text is accessible in the request whether it is send by a regular POST request or a POST request from a HTML form. Both options should work, because all data should be received and send as JSON payload now. Thatâs another aspect of REST, which itself is not opinionated about the payload format (JSON, XML), but once you have chosen a format (here JSON), you should stick to it for your entire API.
Note that all data that comes with the request objectâs body tag isnât typed yet. Everything comes as a JSON string. In the case of the messageâs text , we are doing fine with just keeping it as a string. However, for other types you would have to convert the JSON string:
In this last step, we have used a built-in Express middleware and made it available on an application-level â which means that each request that arrives at one of our Express routes goes through the middleware. Therefore, all data send by a client to our server is available in the incoming requestâs body. Try it by creating a message yourself: In a cURL request you can specify HTTP headers with the -H flag â thatâs how we are saying we want to transfer JSON â and data as payload with the -d flag. You should be able to create messages this way:
You should see the created messaged returned to you on the command line. You can double check whether the message was really created in your messages object (aka pseudo database) by performing another cURL requests on the command line:
There you should see the new message which has been created for you. In addition, you should also be able to request your new message by identifier. Perform the following cURL request to get a single message entity, but use your actual message identifier for it, because my identifier is different from yours:
Thatâs it. You have created your first resource (message) via your REST API and requested the same resource (message(s)) from your REST API. On top of that, you have used a built-in Express middleware to make the data available in the requestâs body object.
So far, we have only imported third-party Express middleware (CORS) or used a built-in Express middleware (body parser) â both on an application-level. Now, letâs build a custom Express middleware ourselves, which will be used on an application-level too. The blueprint for a middleware is similar to the Express functions we have seen before:
A middleware is just a JavaScript function which has access to three arguments: req , res , next . You already know req and res â they are our request and response objects. In addition, the next function should be called to signalize that the middleware has finished its job. In between of the middleware function you can do anything now. We could simply console.log() the time or do something with the request ( req ) or response ( res ) objects.
In our particular case, when creating a message on the message resource, we need to know who is creating the message to assign a userId to it. Letâs do a simple version of a middleware that determines a pseudo authenticated user that is sending the request. In the following case, the authenticated user is the user with the identifier 1 which gets assigned as me property to the request object:
Afterward, you can get the authenticated user from the request object and append it as message creator to the message:
You can imagine how such middleware could be used later to intercept each incoming request to determine from the incoming HTTP headers whether the request comes from an authenticated user or not. If the request comes from an authenticated user, the user is propagated to every Express route to be used there. Thatâs how the Express server can be stateless while a client always sends over the information of the currently authenticated user.
Being a stateless is another characteristic of RESTful services. After all, it should be possible to create multiple server instances to balance the incoming traffic evenly between the servers. If you heard about the term load balancing before, thatâs exactly whatâs used when having multiple servers at your hands. Thatâs why a server shouldnât keep the state (e.g. authenticated user) â except for in a database â and the client always has to send this information along with each request. Then a server can have a middleware which takes care of the authentication on an application-level and provides the session state (e.g. authenticated user) to every route in your Express application.
Now, that you have learned the essentials about application-level middleware in Express, letâs implement the last routes to complete our applicationâs routes. What about the operation to delete a message:
Here we used a dynamic object property to exclude the message we want to delete from the rest of the messages object. You can try to verify the functionality with the following cURL command:
The update operation on a message resource is for you to implement yourself as an exercise. I will spare it for a later section, because it quickly raises a new topic: permissions. The question: Who is allowed to edit a message? It should only be possible for the authenticated user ( me ) who is the creator of the message.
Last, since you have already the pseudo authenticated user at your hands due to the application-wide middleware, you can offer a dedicated route for this resource too:
Itâs the first time you break the rules of being entirely RESTful, because you offer an API endpoint for a very specific feature. It will not be the first time you break the laws of REST, because most often REST is not fully implemented RESTful but rather RESTish. If you want to dive deeper into REST, you can do it by yourself. HATEOAS and other REST related topics are not covered in detail and implemented here.
At the moment, all of our implementation sits in the src/index.js file. However, at some point you may want to modularize your implementation details and put them into dedicated files and folders whereas the src/index.js file should only care about putting everything together and starting the application. Before we dive into modularizing the routing, letâs see how we can modularize our sample data in so-called models first. From your root folder type the following commands to create a folder/file structure for the models.
The models folder in an Express application is usually the place where you define your data sources. In our case, itâs the sample data, but in other applications, for instance, it would be the interfaces to the database. In our case of refactoring this, letâs move our sample data over to the new src/models/index.js file:
Remove the sample data afterward in the src/index.js file. Also import the models in the src/index.js file now and pass them in our custom application-level middleware to all routes via a dedicated context object. Thatâs where the me user (authenticated) user can be placed as well. You donât need necessarily the context object as container, but I found it a good practice to keep everything that is passed to the routes at one place.
Then, instead of having access to the sample data in all routes from outside variables as before â which is an unnecessary side-effect and doesnât keep the function pure â, we want to use the models (and authenticated user) from the functionâs arguments now:
We are using the application-wide middleware to pass the models to all our routes in a context object now. The models are living outside of the src/index.js file and can be refactored to actual database interfaces later. Next, since we made the routing independent from all side-effects and pass everything needed to them via the request object with the context object, we can move the routes to separated places too.
So far, you have mounted routes directly on the Express application instance in the src/index.js file. This will become verbose eventually, because this file should only care about all the important topics to start our application. It shouldnât reveal implementation details of the routes. Now the best practice would be to move the routes into their dedicated folder/file structure. Thatâs why we want to give each REST resource their own file in a dedicated folder. From your root folder, type the following on the command line to create a folder/file structure for the modular routes:
Then, assumed the routes would be already defined, import the all the modular routes in the src/index.js file and use them to mount them as modular routes. Each modular route receives a URI which in REST is our resource:
In our src/routes/index.js entry file to the routes module, import all routes form their dedicated files (that are not defined yet) and export them as an object. Afterward, they are available in the src/index.js file as we have already used them.
Now letâs implement each modular route. Start with the session route in the src/routes/session.js file which only returns the pseudo authenticated user. Express offers the Express Router to create such modular routes without mounting them directly to the Express application instance. Thatâs how we can create modular routes at other places than the Express application, but import them later to be mounted on the Express applicationâs instance as we already have done in a previous step.
Next, the user route in the src/routes/user.js file. Itâs quite similar to the session route:
Notice how we donât need to define the /users URI (path) but only the subpaths, because we did this already in the mounting process of the route in the Express application (see src/index.js file). Next, implement the src/routes/message.js file to define the last of our modular routes:
Every of our modular routes from Express Router is mounted to our Express application with a dedicated URI in the src/index.js file now. The modular routes in the src/routes folder only take care of their sub paths and their implementation details while the mounting in the src/index.js file takes care of the main path and the mounted modular route that is used there. In the end, donât forget to remove all the previously used routes that we moved over to the src/routes/ folder in the src/index.js file.