Beginner's guide to using Node.js and the Express.js framework
ADVERTISEMENT
Table of Contents
- Introduction
- Installing Express.js
- What is REST?
- Some Benefits of REST?
- Node.js modules
- Creating a hello world Node.js / Express.js application
- Declaring API endpoints using Express.js
- Requests and responses
- Commonly used response methods
- Status codes and redirects
- Error Handling in Express.js
- Environment variables and ports
- HTTP headers
- Structuring a Node.js and Express.js application
- Serving static files
- CORS
- Handling and combining JSON objects using Express.js
- Middleware
- API security using Express
- Conclusion
Introduction
In this article, we will present a tutorial for using the Node.js and Express.js frameworks. These frameworks are used to execute Javascript code on the server-side and are used to build fast and scalable server applications.
Download and Install Node.js by choosing your Operating System (Windows, Linux, etc.) from the list here: https://nodejs.org/en/download/
After downloading Node.js, now you are equipped and ready to use the Express.js framework. To setup Express.js, you will need to create a Node.js project to get started.
Execute the following commands:
mkdir my-node-express-app
cd my-node-express-app
npm init
Provide responses for the required fields (name and version), as well as the main field:
- name: The name of your Node.js project.
- version: The initial project version. We recommend the following semantic versioning guidelines and starting with 1.0.0.
- main: The name of the file that will be loaded when your module is required by another application. The default name is index.js. Since we are using Express.js, we will name our file app.js as the entry point to our application. A standard when building Express applications.
Installing Express.js
Now you are ready to install Express.js, as you have set up a Node.js project and are ready to get started. Install Express by executing the command below inside your terminal.
npm install --save express
Voila`! Easy as that. You are now ready to get started using the Express.js framework, and using it to build contemporary new-age REST web services.
What is REST?
REST, or Representational State Transfer is a method of implementing a Web Service. It is the ideology that everything is a resource. Every resource is identified by a unique uniform resource indicator (URI). An important point to note with REST is that it is stateless. What this means is that when interacting with the REST web service to retrieve data, you will receive only the most current state of the resource. For example, if you send another request, the data received may be different from your first request. REST APIs predominantly is implemented using HTTP, however, it’s also possible to use it with other protocols.
Some Benefits of REST?
- Flexibility - Data is not tied to resources or methods
- Scalable - Supports large numbers of requests
- Reliable - No single point of failure
- Handles Common Data Formats - such as JSON
{"apple":5,"pear":5,"grapes":25,"strawberries":40}
The above data is the inventory items and their associated prices. This data can be parsed and used by other systems. It is in JSON format (Javascript-Object-Notation), an open-standard file format that uses human-readable text.
Node.js modules
You can consider modules to be the same as JavaScript libraries. They are simply a set of functions you want to include in your application. Express.js itself is a module, and the middleware and database libraries that we use in our Express applications are also Node.js modules. Keep that in-mind!
You can learn how to build your own Node.js module here - for later.
Creating a hello world Node.js / Express.js application
The most simple Node Express application you can build is a hello world example.
This will provide you with the essentials, and a starting point to build any REST API.
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
Reference - straight from the docs`
Typically, I find it best to analyse a program of this size, line-by-line. It helps us to understand the starting point, and we can start building from a better foundation.
- Line #1 - Here is where we load in our express dependency so we have a reference point to start using the framework itself.
- Line #2 - Here we instansiate our express application
- Line #3 - We declare the port number where the express server will be running on, eg. localhost:3000
- Line #4 - This is where we declare our first Express.js route, or “API endpoint”.
- Line #5 - We start up the server and it starts running on the port specified, in our case, 3000.
Declaring API endpoints using Express.js
Declaring API endpoints in your Express application will be specific to your application requirements. It may be that you are required to only to handle user details, so receiving a POST request, and to store those details in some type of database. The HTTP methods which are supported are, GET, POST, PUT, DELETE. For a full list, see app.METHOD here. When thinking about the architecture of your Express application, try to encapsulate if your route requires any middleware functionality within the cycle. Routes are often used in conjunction with middlewares in Express, most commonly, error handling. Middleware is supported by Express, typically this can be implemented using the App.use()
functionality and defining your middleware routine.
Another concept you would like to consider when architecting an Express application is using Router
. This allows you to split your Express application up into smaller components, as the router acts as a stand-alone mini-application. Each component can then imported into the app.js
file and used. Routes themselves, are simply callback functions, and it is possible to get creative with your route paths.
Note: There is some really cool pattern matching that can be done with routes.
Requests and responses
Express.js has two main objects when handling the client-server communication. One is the "Request object" which contains information specifically related to HTTP, such as the HTTP header, body, and query parameters. For example, if the client sends a request to the server “eg. apples=5”, this information can be extracted from the request (or req
, as express calls it) object, then it can be used inside your route declarations.
Some common examples of extracting data from the request object are - req.cookies
, req.hostname
, req.body
, req.params
, req.secure
, and so on.
A full list of methods from the request object can be found here.
The other main objective is the "Response object", which is the inverse to the Request object. It is the outgoing object sent from Express to the requester. The response object contains information that is sent to the client-side. This might be a string of text sent back to the browser, showing hello world!
, a status code of 200, cookie information, etc. It contains methods used to formulate a response worthy of the application.
Commonly used response methods
Sending a success status code
The HTTP 200 status code refers to SUCCESS. Communication has been successfully established with the server and is sent an OK back from the server.
res.send(200)
Sending a redirect
res.redirect('\to-another-page')
We may want to redirect to another page, after performing some data operations or processing. The redirect method is used from the response object to redirect to a different route.
Sending a 404 redirect
res.status(404).send('woops! Page does not exist!')
Express response route might be something we are all more than familiar with, the old, woops, page does not exist!
404 redirect. This specific information is sent through with the response object,
It is possible to send both, the status, and a message in one line which is super gratifying.
Sending JSON data
res.send({ user: ‘bob’})
A nice and simple way to send JSON data from the server using express.
Setting a cookie in a response
res.cookie('name', 'john', { domain: '.example.com', path: '/admin', secure: true })
Ending the response process
res.end()
Initiating a download of resource or file
res.download(‘/my-report.pdf
)`
Typically the browser will prompt the user before downloading the file when hitting the API endpoint.
It is used to gracefully handle the response process without sending any data.
Status codes and redirects
HTTP status codes are used extensively in production-grade Express applications, as they provide a mechanism for the server to issue a basic response to the client. Status codes are especially used in the network transport layer. For example, status code 200, can be interpreted as “everything went OK”. There is a class of status codes, and they are split up within certain ranges. I have broken it down as an easy-to-remember list:
Status code ranges:
- 100s Informational
- 200s Success.
- 300s Redirection
- 400s Client error
- 500s Server error
Status codes are often chosen to what is most appropriate to the scenario. For example, if the request is received successfully, then we may want to send the 200 status code. Otherwise, if there is a problem with the route, we may want to send back the 500 status, indicating that there is an error with our Express server, for example.
Redirects are often used together with status codes in the 3xx band. One example, they are used to redirect a webpage which has been moved to another location, by using the 301 redirect status code.
res.redirect(301, 'http://example.com')
The Express redirect method can even handle a fully-qualified URL, redirecting the browser to a different site:
res.redirect('http://zeroequalsfalse.com')
Error Handling in Express.js
Express comes out-of-the-box with an error handler, which means you can get started without having to do most of the heavy lifting. Express' in-built error handler performs handling around the routing and the middleware process.
Basic error handling
app.get('/', function (req, res) {
throw new Error('There has been an error')
})
This is an example of how to throw errors when writing synchronous code. Express will catch it and perform the necessary actions.
Alternatively, for asynchronous code, using the next
function will pass an error to Express.
app.get('/', function (req, res, next) {
fs.readFile('/file-not-there', function (err, data) {
if (err) {
next(err)
} else {
res.send(data)
}
})
})
Read more on Error handling here
Environment variables and ports
Ports and environment variables are commonly used in production-grade express applications.
You can specifically set your PORT
variable for your Express application to listen to. The default port number is 3000, however, on some systems, you may want your express app to listen within a specific range of ports, so be wary and avoid server port conflicts.
You can set the port manually by:
app.set('port', process.env.PORT || 3000)
This will tell express environment to use the port number in the environment variable PORT
. This can be set by using the terminal:
PORT=8080
Or, by passing the port number into your Node / Express application:
PORT=8080 node app.js
This will then set the port number for your express application to 8080 instead of the default 3000 (from the code above).
HTTP headers
It is possible to set HTTP headers using the Express framework. This is really awesome if you want to change data formats, sending data back from your Express application.
How to do this is simple:
res.set(‘Content-Type', 'application/json')
You can use the set
method to set specific values of the HTTP header for your response object. The response sending to client-side from the Express application. There is more values you can set besides the Content-Type
, they are as follows:
res.set('Content-Length', '127')
This sets the HTTP header Content-Length
to specify the response will be 127, which will be the number of octects that the message of the body represents.
res.set('Keep-Alive', 'true')
Setting the trust proxy will set the HTTP header value for eg. X-forwarded-For : 127.0.0.1
may be trusted. This will provide express with a remote client IP address.
res.set('trust proxy', '127.0.0.1')
Setting a custom header…
res.set('X-Hello', 'World')
There are a plethora of ways that HTTP headers can be modified in the response object, inside your Express application. It definitely helps to get a feel for how they work by setting them yourself, and analyzing the response codes, using a tool like Wireshark.
Structuring a Node.js and Express.js application
Structuring a Node Express application is uber important as it provides a method for developers to build their application in a way that can be maintainable and easy to expand.
Here is the structure of a simple Model-View-Controller (MVC) Node Express app. A simple Create-Retrieve-Update-Delete (CRUD) application.
project/
controllers/
users.js
util/
plugin.js
middlewares/
auth.js
models/
user.js
routes/
user.js
router.js
public/
js/
css/
img/
views/
users/
index.pug
tests/
users/
create-user-test.js
update-user-test.js
get-user-test.js
.gitignore
app.js
package.json
models: models sit between the controller and the database. It is possible to build out your model schema before connecting to your database. You can use great ORM tools like Mongoose which can help you to build the schema.
routes: We have only looked at a basic hello world app so far, however, for production-grade Node express applications, we split up our routes and keep them all within the routes folder. For example, you can define everything related to a user in the user.js file, containing user operations and sub-routes of the application.
router.post('/users/create', controller.create)
router.put('/users/:userName', controller.update)
router.get('/users', controller.getAll)
controllers: This is where you specify your route handlers, where your routes are going, and the encapsulated business logic.
util: This contains utility/helper functions that can be used by any controller. For example, you can write a function like calculateEuclideanDistance(a, b).
middlewares: You can write middlewares to dissect all incoming requests before sending the request to the route handler. For example,
router.post('/login', auth, controller.login)
where auth is a middleware function defined in middlewares/auth.js to handle the authentication process.
public: The public folder is used to store static files. These are typically index.html
, images in /img
, javascript files in /js
, and CSS files /CSS
.
views: Contains templates to be rendered by the server. There are many template engines like, pug, jade, etc. This will be where you can define the templates, which are used to update your front-end with data from the model.
tests: The tests folder covers all application testing files.
app.js: This is the main file of your Express application, it pulls together your entire Express application.
package.json: This file contains information about the project dependencies, scripts and testing, and versioning.
Serving static files
Serving static files using Express.js is simple:
app.use(express.static('public'))
Most Express applications use the folder public
to store static files. Again, these are typically index.html
, images in /img
, javascript files in /js
, and CSS files /css
. However, you can store any other files you wish to expose publically from your Express application. This could be my-report.pdf
, for example, accessed from https://my-developer-profile.com/my-report.pdf
.
To serve separate static files using the Express.js framework, we can leverage the common npm libraries, serve-index, and serve-favicon. These libraries will help us bootstrap serving static files in Express.
npm install --save serve-index
npm install --save serve-favicon
CORS
Javascript applications running in the browser can only access resources within the same domain. By default, browsers restrict cross-origin HTTP requests. Loading js scripts, images, and styles are perfectly ok. However, XHR and Fetch calls to another domain/server will almost certainly fail and be blocked by the browser.
Only unless… the domain you are trying to access the resources from has specifically implemented a method to allow the connection.
This is drum roll please, CORS.
CORS or Cross-Origin Resource Sharing is a mechanism to tell the browser to give a web application running at one origin, access to selected resources from a different origin. It does this by using additional HTTP headers. For example, https://blog.app.com uses XMLHttpRequest
to make a request for https://shop.app.com/data.json.
The server which implements CORS does so by modifying their HTTP response headers. To do this in the Express framework, we do the following:
npm install --save cors
var cors = require('cors')
…
app.use(cors())
Handling and combining JSON objects using Express.js
JSON format (Javascript-Object-Notation), an open-standard file format that uses human-readable text. JSON can be used in an Express application by converting it into a Javascript Object. Afterward, your Express application can use the data inside the object to perform data processing, save to a database, and so on.
You can specify in your Express application routes HTTP header request and responses, to accept Content-type: application/JSON
which allow Express to handle JSON.
A common method you may find useful is accessing the data inside the JSON object. This is typically done by installing the body-parser
library and extracting the data from the JSON object via the Request object. This looks like the following:
npm install --save body-parser
var bodyparser = require('body-parser')
// create application/json parser
var jsonParser = bodyParser.json()
app.post('/api/users', jsonParser, function (req, res) {
// create user in req.body
})
You will notice the user information is stored and can be accessed now through req.body
and the JSON parser
is used as middlewares within the request. The body field can now be accessed, which might contain information in our case about a user. This can look like req.body.userName
or req.body.userID
.
At some stage, you may be required to combine data from multiple data sources and return a single JSON object. This can be done with ease in Node.js / Express.
app.get('/combined', (_, res) => {
const json1 = { name: 'John', age: 31, city: 'New York' };
const json2 = { relative: 'Ann', age: 50, city: 'San Francisco' };
const json3 = {
...json1,
...json2
};
res.send(json3);
res.end();
});
Another way of combing JSON data is by extracting specific data fields from the request, and then building a custom JSON object. For example, a bus/route application:
var name = req.body.name
var bus driver name = driverName //a global variable
var text = `{ passengers: { “passenger”:` + name + `}, { “driverName”:` + busDriverName + `} }`
var obj = JSON.parse(text)
Middleware
The middlewares have access to a Node / Express application by having access to both the request and response objects. Express also provides the next()
function which invokes the next middleware function in the request and response cycle. This makes it possible for a request object, OR response, to go through a series of sanitizations or data processing steps to finally end the route.
Here is an example of using middleware within Express.js. An application that stores only item names starting with the letter ‘A’. A single middleware step for a single express route.
var express = require('express')
var app = express()
var storeItemStartingA = function (req, res, next) {
// Data insertion step
// if req.body.itemName starts with A, insert to DB
// ...
console.log('DATA SAVED')
next()
}
app.use(storeItemStartingA)
app.post('/items/:itemName', function (req, res) {
res.send('Thank you! We will be back in touch with you soon')
})
app.listen(3000)
The storeItemStartingA
is a middleware function that will be applied to every item posted to the /items/ endpoint, inside the request object.
API security using Express
Securing production-grade Express applications is essential, as there are some common vulnerabilities that can be exploited using the web and HTTP. In this section, we will look at how to prevent some common attacks and some npm libraries we can leverage to help us get set up much faster, without doing the heavy lifting.
Preventing DOS attacks
DOS or Denial-of-Service attack is a common attack, where the attacker overloads the HTTP server by requesting a godlike amount of data, at a rapid pace. This becomes too much for the server to handle and it is then forced to shut down or go offline.
We can help to prevent a DOS attack by taking these simple steps:
Limit the rate of requests
npm install --save express-rate-limit
const rateLimit = require("express-rate-limit");
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);
The above code uses the rate limit library to limit the number of requests of each IP address. This provides our Express app with a good baseline protection method of stopping a huge flurry of requests flooding the webserver.
Blocking known IP addresses
Another method to mitigate the DOS attack is preventing known attackers or known IP addresses. This can be achieved if you have the IP address of the requester making the suspicious number of requests.
const ipBlock = require('express-ip-block');
const ips = ['216.27.61.137'];
const options = { allowForwarded: true };
app.get('/', ipBlock(ips, options), (request, response, next) => response.render('index'));
Protecting from HTTP header injections
HTTP headers are notoriously vulnerable for attackers to inject and manipulate data to gain hijack the web server. For example, cross-site scripting (XSS) is a common method used on the web, injecting malicious code into the Referer header or cookie payload. An attack would include the code in the request object and the web server then, unknowingly, embeds the malicious code within the response object.
Request:
http://example.com/redirect.asp?origin=bob
Response:
HTTP/1.1 302 Object moved
Date: Tues, 13 Mar 2020 15:32:13 CST
Location: account.asp?origin=bob
Connection: close
Content-Length: 140
To mitigate these type of attacks and keep your HTTP headers safe, use helmet
. This is recommended by Express.js as it provides validation checking using a suite of software libraries, and is trusted by the community.
npm install --save helmet
const helmet = require('helmet')
const app = express()
app.use(helmet())
Conclusion
In this article, we presented a tutorial for using Node.js and the Express.js frameworks.
Final Notes
Recommended product: Coding Essentials Guidebook for Developers