Post

NodeJS Express HTTP Server

In this post we’ll look at how to build a simple HTTP server in NodeJS using the express framework.

  1. Server Setup
  2. Express Basics
  3. More Advanced Stuff

Server Setup

Required Packages

First step is to install the required npm packages:

1
npm i express body-parser

Basic Setup

Once the packages are installed, we can use the below code to import the packages and setup our first server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');

// Create new Express Server
const app = express();
const server = require('http').Server(app);

// Server Configuration
const PORT = 8080;

// Support JSON encoded POST request bodies
app.use(bodyParser.json());

// Start the Server
server.listen(PORT, () => {
	console.log(`[-] Server Listening on Port ${PORT}`);
});

Nice, our server is now setup! But it’s not very useful as it doesn’t actually do anything. Let’s learn how to setup routes/endpoints so we can listen for request and send back data.


Express Basics

Routes

Here is how we create a basic route:

  • The first parameter is a string containing the route path (endpoint).
  • The second parameter is a callback that will be executed when a request is made to the route.
1
2
3
4
5
6
7
8
9
// GET Example
app.get('/', (req, res) => {
	res.send('GET Hello!');
});

// POST Example
app.post('/', (req, res) => {
	res.send('POST Hello!');
});

The functions names get & post can be changed to any valid HTTP method (OPTIONS, PUT, DELETE, etc) in lowercase.

Sending Back Data

When the callback function is executed, two parameters will be passed in for us to use. These are the req and res parameters short for request and response:

  • req contains information about the request such as headers and any data in the body that may have been sent.
  • res is how we send data back to the client, using the the send and sendFile methods.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// The type will automatically be set as HTML if a string is provided.
app.get('/home', (req, res) => {
	res.send('<h1>Home</h1>');
});

// The content-type will automatically be set as JSON if an object is provided.
app.get('/json', (req, res) => {
	res.send({ json: true });
});

// You can also specify the type manually
app.get('/text', (req, res) => {
	res.type('txt');
	res.send('<h1>Home</h1>');
});

// Return a file from disk
app.get('/home', (req, res) => {
	res.sendFile(path.join(__dirname, 'index.html'));
});

Response Codes

When data is sent back using the send or sendFile methods a response code of 200 is used. We can specify a custom code using the status and sendStatus methods:

  • If you want to change the status code AND send data back in the body, use the status method.
  • If you only want to send a status code with no body, use the sendStatus method.
1
2
3
4
5
6
7
8
9
app.get('/json', (req, res) => {
	// Send custom HTTP Status code
	res.status(406).send({ json: true });
});

app.get('/admin', (req, res) => {
	// Send custom HTTP Status code
	res.sendStatus(404);
});

Route Parameters

We use Route Parameters as a placeholder variable for data that will be supplied by the user:

  • If the client calls the API /user/53, we want to be able to access the value 53.
  • We can do this by specifying a Route Parameter like this /user/:userId.
  • Route parameters are accessible using req.params. This returns an object of all the route parameters.
  • Using req.params.userId will return 53.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Route with a Route Parameter
app.get('/user/:userId', (req, res) => {
	// For the URI: /user/53
	console.log(req.params); // { userId: '53' };
	console.log(req.params.userId)  // '53'

	res.sendStatus(200);
});

// Multiple Route Parameters
app.get('/light/:lightbulbId/:action', (req, res) => {
	// For the URI: /light/17/off
	console.log(req.params); // { lightbulbId: '17', action: 'off' };
	console.log(`Light ${req.params.lightbulbId} will be turned ${req.params.action}`)  // 'Light 17 will be turned off'

	res.sendStatus(200);
});

Query Parameters

Query Parameters can be supplied by the client can be accessed using req.query. An object containing all the query parameters will be returned:

1
2
3
4
5
6
7
8
// Query Parameters Example
app.get('/search', (req, res) => {
	// For the URI: /search?firstName=John&lastName=Smith
	console.log(req.query) // { firstName: 'John', lastName: 'Smith' }

	// Send custom HTTP Status code
	res.sendStatus(200);
});

Cookies

We can set a cookie in the response by using the cookie method:

  • The first parameters is the cookie name MyCookie.
  • The second is the cookie value abcdefghijklmnopqrstuvwxyz.
  • The third is an object of cookie attributes.

After using the cookie method, either send or sendStatus must be called as well to return the result.

1
2
3
4
5
6
7
8
9
10
app.get('/home', (req, res) => {
	res.cookie('MyCookie', 'abcdefghijklmnopqrstuvwxyz', {
		maxAge: 3600000, // Time in milliseconds: 3600000 = 1 Hour
		domain: 'localhost',
		path: '/',
		secure: false, 
		httpOnly: true,
		sameSite: 'strict'
	}).send({ success: true });
});

Multiple cookies can be set by calling the cookie method multiple times.

JSON Body

If the client has sent us data in JSON format, here is how we can access that data from the request:

  • req.body will return the JSON data in the request body in the form of an object.
  • NOTE 1: if req.body is undefined, ensure that you have app.use(bodyParser.json()); in your code.
  • NOTE 2: if req.body is an empty object, ensure that the Content-Type in the request header is application/json.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Login API
app.post('/login', (req, res) => {
	console.log(req.body); // { username: 'John123', password: 'Password_ThatShouldNotBeGuessed641'}

	console.log(req.body.username); // 'John123'
	console.log(req.body.password); // 'Password_ThatShouldNotBeGuessed641'

	let loginSuccessful = false;

	if (req.body && req.body.username && req.body.password) {
		// Insert Authentication logic here
		loginSuccessful = checkLogin(req.body.username, req.body.password)
	}

	if (loginSuccessful) {
		// Login Successful, set a Cookie in the response
		res.cookie('auth-token', 'kln450x02sl5gusnh3mpu0s7ca2jfsaf8h', {
			maxAge: 3600000, 	// Time in milliseconds: 3600000 = 1 Hour
			httpOnly: true,
			sameSite: 'lax'
		}).send({ success: true });
	} else {
		// Login Failed
		res.send({ success: false });
	}
});


More Advanced Stuff

Custom Headers

Custom response headers can be added using the header method to add controls such as CORS, HSTS, CSP, etc:

  • The first parameter is the header name.
  • The second parameter is the header value.
1
2
3
4
5
app.get('/home', (req, res) => {
	res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:8080');

	res.sendFile(path.join(__dirname, 'index.html'));
});

Next Route

A common situation that can occur is when you need to do the same thing in multiple routes. An example of this might be adding a header, or checking if a user is authenticated.

Adding these headers/checks in every route is bad practice and can lead to security issues if you forget to add it in one of routes.

Luckily there is a better way:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Route 1
app.use((req, res, next) => {
	res.header('Custom-Header', 'NodeJS Server');
	next();
})

// Route 2
app.get('/home', (req, res) => {
	res.sendFile(path.join(__dirname, 'index.html'));
});

// Route 3
app.use((req, res, next) => {
	// Insert logic to check if the user's cookie is valid
	let authed = true;

	if (authed) {
		next();
	} else {
		res.sendStatus(404);
	}
})

// Route 4
app.get('/authed/home', (req, res) => {
	res.sendFile(path.join(__dirname, 'home.html'));
});

The use function matches every route, so putting a app.use before all other routes will ensure it gets called first.

When we request /home the following will happen:

  • Route 1 is be matched, custom response header is be added, and the next function is called.
  • The next function means the route is finished doing what it needs to, but it doesn’t want to send a response. Express will continue checking for the next route that matches.
  • Route 2 is matched and returns index.html, completing the request.

When we request /authed/home the following will happen:

  • Route 1 is be matched, custom response header is be added, and the next function is called.
  • Route 2 is NOT matched.
  • Route 3 is matched. This route checks to see if the user authorized to view the subsequent pages. If yes, then next is called. If not, then a 404 is sent to the client.
  • If the user is authorised, then Route 4 is matched and returns home.html, completing the request.
This post is licensed under CC BY 4.0 by the author.