Linux Professional Institute Learning Logo.
Skip to main content
  • Home
    • All Resources
    • LPI Learning Materials
    • Become a Contributor
    • Publishing Partners
    • Become a Publishing Partner
    • About
    • FAQ
    • Contributors
    • Translations
    • Contact
  • LPI.org
035.2 Lesson 1
Topic 031: Software Development and Web Technologies
031.1 Software Development Basic
  • 031.1 Lesson 1
031.2 Web Application Architecture
  • 031.2 Lesson 1
031.3 HTTP Basics
  • 031.3 Lesson 1
Topic 032: HTML Document Markup
032.1 HTML Document Anatomy
  • 032.1 Lesson 1
032.2 HTML Semantics and Document Hierarchy
  • 032.2 Lesson 1
032.3 HTML References and Embedded Resources
  • 032.3 Lesson 1
032.4 HTML Forms
  • 032.4 Lesson 1
Topic 033: CSS Content Styling
033.1 CSS Basics
  • 033.1 Lesson 1
033.2 CSS Selectors and Style Application
  • 033.2 Lesson 1
033.3 CSS Styling
  • 033.3 Lesson 1
033.4 CSS Box Model and Layout
  • 033.4 Lesson 1
Topic 034: JavaScript Programming
034.1 JavaScript Execution and Syntax
  • 034.1 Lesson 1
034.2 JavaScript Data Structures
  • 034.2 Lesson 1
034.3 JavaScript Control Structures and Functions
  • 034.3 Lesson 1
  • 034.3 Lesson 2
034.4 JavaScript Manipulation of Website Content and Styling
  • 034.4 Lesson 1
Topic 035: NodeJS Server Programming
035.1 NodeJS Basics
  • 035.1 Lesson 1
035.2 NodeJS Express Basics
  • 035.2 Lesson 1
  • 035.2 Lesson 2
035.3 SQL Basics
  • 035.3 Lesson 1
How to get certified
  1. Topic 035: NodeJS Server Programming
  2. 035.2 NodeJS Express Basics
  3. 035.2 Lesson 1

035.2 Lesson 1

Certificate:

Web Development Essentials

Version:

1.0

Topic:

035 NodeJS Server Programming

Objective:

035.2 NodeJS Express Basics

Lesson:

1 of 2

Introduction

Express.js, or simply Express, is a popular framework that runs on Node.js and is used to write HTTP servers that handle requests from web application clients. Express supports many ways to read parameters sent over HTTP.

Initial Server Script

To demonstrate Express’s basic features for receiving and handling requests, let’s simulate an application that requests some information from the server. In particular, the example server:

  • Provides an echo function, which simply returns the message sent by the client.

  • Tells the client its IP address upon request.

  • Uses cookies to identify known clients.

The first step is to create the JavaScript file that will operate as the server. Using npm, create a directory called myserver with the JavaScript file:

$ mkdir myserver
$ cd myserver/
$ npm init

For the entry point, any filename can be used. Here we will use the default filename: index.js. The following listing shows a basic index.js file that will be used as the entry point for our server:

const express = require('express')
const app = express()
const host = "myserver"
const port = 8080

app.get('/', (req, res) => {
  res.send('Request received')
})

app.listen(port, host, () => {
  console.log(`Server ready at http://${host}:${port}`)
})

Some important constants for the server configuration are defined in the first lines of the script. The first two, express and app, correspond to the included express module and an instance of this module that runs our application. We will add the actions to be performed by the server to the app object.

The other two constants, host and port, define the host and communication port associated to the server.

If you have a publicly accessible host, use its name instead of myserver as the value of host. If you don’t provide the host name, Express will default to localhost, the computer where the application runs. In that case, no outside clients will be able to reach the program, which may be fine for testing but offers little value in production.

The port needs to be provided, or the server will not start.

This script attaches only two procedures to the app object: the app.get() action that answers requests made by clients through HTTP GET, and the app.listen() call, which is required to activate the server and assigns it a host and port.

To start the server, just run the node command, providing the script name as an argument:

$ node index.js

As soon as the message Server ready at http://myserver:8080 appears, the server is ready to receive requests from an HTTP client. Requests can be made from a browser on the same computer where the server is running, or from another machine that can access the server.

All transaction details we’ll see here are shown in the browser if you open a window for the developer console. Alternatively, the curl command can be used for HTTP communication and allows you to inspect connection details more easily. If you are not familiar with the shell command line, you can create an HTML form to submit requests to a server.

The following example shows how to use the curl command on the command line to make an HTTP request to the newly deployed server:

$ curl http://myserver:8080 -v
* Trying 192.168.1.225:8080...
* TCP_NODELAY set
* Connected to myserver (192.168.1.225) port 8080 (#0)
> GET / HTTP/1.1
> Host: myserver:8080
> User-Agent: curl/7.68.0
>Accept: /
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: text/html; charset=utf-8
< Content-Length: 16
< ETag: W/"10-1WVvDtVyAF0vX9evlsFlfiJTT5c"
< Date: Fri, 02 Jul 2021 14:35:11 GMT
< Connection: keep-alive
<
* Connection #0 to host myserver left intact
Request received

The -v option of the curl command displays all the request and response headers, as well as other debugging information. The lines starting with > indicate the request headers sent by the client and the lines starting with < indicate the response headers sent by the server. Lines starting with * are information generated by curl itself. The content of the response is displayed only at the end, which in this case is the line Request received.

The service’s URL, which in this case contains the server’s hostname and port (http://myserver:8080), were given as arguments to the curl command. Because no directory or filename is given, these default to the root directory /. The slash turns up as the request file in the > GET / HTTP/1.1 line, which is followed in the output by the hostname and port.

In addition to displaying HTTP connection headers, the curl command assists application development by allowing you to send data to the server using different HTTP methods and in different formats. This flexibility makes it easier to debug any problems and implement new features on the server.

Routes

The requests the client can make to the server depend on what routes have been defined in the index.js file. A route specifies an HTTP method and defines a path (more precisely, a URI) that can be requested by the client.

So far, the server has only one route configured:

app.get('/', (req, res) => {
  res.send('Request received')
})

Even though it is a very simple route, simply returning a plain text message to the client, it is enough to identify the most important components that are used to structure most routes:

  • The HTTP method served by the route. In the example, the HTTP GET method is indicated by the get property of the app object.

  • The path served by the route. When the client does not specify a path for the request, the server uses the root directory, which is the base directory set aside for use by the web server. A later example in this chapter uses the path /echo, which corresponds to a request made to myserver:8080/echo.

  • The function executed when the server receives a request on this route, usually written in abbreviated form as an arrow function because the syntax => points to the definition of the nameless function. The req parameter (short for “request”) and res parameter (short for “response”) give details about the connection, passed to the function by the app instance itself.

POST Method

To extend the functionality of our test server, let’s see how to define a route for the HTTP POST method. It’s used by clients when they need to send extra data to the server beyond those included in the request header. The --data option of the curl command automatically invokes the POST method, and includes content that will be sent to the server via POST. The POST / HTTP/1.1 line in the following output shows that the POST method was used. However, our server defined only a GET method, so an error occurs when we use curl to send a request via POST:

$ curl http://myserver:8080/echo --data message="This is the POST request body"
* Trying 192.168.1.225:8080...
* TCP_NODELAY set
* Connected to myserver (192.168.1.225) port 8080 (#0)
> POST / HTTP/1.1
> Host: myserver:8080
> User-Agent: curl/7.68.0
>Accept: /
> Content-Length: 37
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 37 out of 37 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< X-Powered-By: Express
< Content-Security-Policy: default-src 'none'
< X-Content-Type-Options: nosniff
< Content-Type: text/html; charset=utf-8
< Content-Length: 140
< Date: Sat, 03 Jul 2021 02:22:45 GMT
< Connection: keep-alive
<
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /</pre>
</body>
</html>
* Connection #0 to host myserver left intact

In the previous example, running curl with the parameter --data message="This is the POST request body" is equivalent to submitting a form containing the text field named message, filled with This is the POST request body.

Because the server is configured with only one route for the / path, and that route only responds to the HTTP GET method, so the response header has the line HTTP/1.1 404 Not Found. In addition, Express automatically generated a short HTML response with the warning Cannot POST.

Having seen how to generate a POST request through curl, let’s write an Express program that can successfully handle the request.

First, note that the Content-Type field in the request header says that the data sent by the client is in the application/x-www-form-urlencoded format. Express does not recognize that format by default, so we need to use the express.urlencoded module. When we include this module, the req object—​passed as a parameter to the handler function—​has the req.body.message property set, which corresponds to the message field sent by the client. The module is loaded with app.use, which should be placed before the declaration of routes:

const express = require('express')
const app = express()
const host = "myserver"
const port = 8080

app.use(express.urlencoded({ extended: true }))

Once this is done, it would be enough to change app.get to app.post in the existing route to fulfill requests made via POST and to recover the request body:

app.post('/', (req, res) => {
  res.send(req.body.message)
})

Instead of replacing the route, another possibility would be to simply add this new route, because Express identifies the HTTP method in the request header and uses the appropriate route. Because we are interested in adding more than one functionality to this server, it is convenient to separate each one with its own path, such as /echo and /ip.

Path and Function Handler

Having defined which HTTP method will respond to the request, we now need to define a specific path for the resource and a function that processes and generates a response to the client.

To expand the echo functionality of the server, we can define a route using the POST method with the path /echo:

app.post('/echo', (req, res) => {
  res.send(req.body.message)
})

The req parameter of the handler function contains all the request details stored as properties. The content of the message field in the request body is available in the req.body.message property. The example simply sends this field back to the client through the res.send(req.body.message) call.

Remember that the changes you make take effect only after the server is restarted. Because you are running the server from a terminal window during the examples in this chapter, you can shut down the server by pressing kbd:[Ctrl+C] on that terminal. Then rerun the server through the node index.js command. The response obtained by the client to the curl request we showed before is now successful:

$ curl http://myserver:8080/echo --data message="This is the POST request body"
This is the POST request body

Other Ways to Pass and Return Information in a GET Request

It might be excessive to use the HTTP POST method if only short text messages like the one used in the example will be sent. In such cases, data can be sent in a query string that starts with a question mark. Thus, the string ?message=This+is+the+message could be included within the request path of the HTTP GET method. The fields used in the query string are available to the server in the req.query property. Therefore, a field named message is available in the req.query.message property.

Another way to send data via the HTTP GET method is to use Express’s route parameters:

app.get('/echo/:message', (req, res) => {
  res.send(req.params.message)
})

The route in this example matches requests made with the GET method using the path /echo/:message, where :message is a placeholder for any term sent with that label by the client. These parameters are accessible in the req.params property. With this new route, the server’s echo function can be requested more succinctly by the client:

$ curl http://myserver:8080/echo/hello
hello

In other situations, the information the server needs to process the request do not need to be explicitly provided by the client. For instance, the server has another way to retrieve the client’s public IP address. That information is present in the req object by default, in the req.ip property:

app.get('/ip', (req, res) => {
  res.send(req.ip)
})

Now the client can request the /ip path with the GET method to find its own public IP address:

$ curl http://myserver:8080/ip
187.34.178.12

Other properties of the req object can be modified by the client, especially the request headers available in req.headers. The req.headers.user-agent property, for example, identifies which program is making the request. Although it is not common practice, the client can change the contents of this field, so the server should not use it to reliably identify a particular client. It is even more important to validate the data explicitly provided by the client, to avoid inconsistencies in boundaries and formats that could adversely affect the application.

Adjustments to the Response

As we’ve seen in previous examples, the res parameter is responsible for returning a response to the client. Furthermore, the res object can change other aspects of the response. You may have noticed that, although the responses we’ve implemented so far are just brief plain text messages, the Content-Type header of the responses is using text/html; charset=utf-8. Although this does not prevent the plain text response from being accepted, it will be more correct if we redefine this field in the response header to text/plain with the setting res.type('text/plain').

Other types of response adjustments involve using cookies, which allow the server to identify a client that has previously made a request. Cookies are important for advanced features, such as creating private sessions that associate requests to a specific user, but here we’ll just look at a simple example of how to use a cookie to identify a client that has previously accessed the server.

Given the modularized design of Express, cookie management must be installed with the npm command before being used in the script:

$ npm install cookie-parser

After installation, cookie management must be included in the server script. The following definition should be included near the beginning of the file:

const cookieParser = require('cookie-parser')
app.use(cookieParser())

To illustrate the use of cookies, let’s modify the route’s handler function with the / root path that already exists in the script. The req object has a req.cookies property, where cookies sent in the request header are kept. The res object, on the other hand, has a res.cookie() method that creates a new cookie to be sent to the client. The handler function in the following example checks whether a cookie with the name known exists in the request. If such a cookie does not exist, the server assumes that this is a first-time visitor and sends it a cookie with that name through the res.cookie('known', '1') call. We arbitrarily assign the value 1 to the cookie because it is supposed to have some content, but the server doesn’t consult that value. This application just assumes that the simple presence of the cookie indicates that the client has already requested this route before:

app.get('/', (req, res) => {
  res.type('text/plain')
  if ( req.cookies.known === undefined ){
    res.cookie('known', '1')
    res.send('Welcome, new visitor!')
  }
  else
    res.send('Welcome back, visitor');
})

By default, curl does not use cookies in transactions. But it has options to store (-c cookies.txt) and send stored cookies (-b cookies.txt):

$ curl http://myserver:8080/ -c cookies.txt -b cookies.txt -v
* Trying 192.168.1.225:8080...
* TCP_NODELAY set
* Connected to myserver (192.168.1.225) port 8080 (#0)
> GET / HTTP/1.1
> Host: myserver:8080
> User-Agent: curl/7.68.0
>Accept: /
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: text/plain; charset=utf-8
* Added cookie known="1" for domain myserver, path /, expire 0
< Set-Cookie: known=1; Path=/
< Content-Length: 21
< ETag: W/"15-l7qrxcqicl4xv6EfA5fZFWCFrgY"
< Date: Sat, 03 Jul 2021 23:45:03 GMT
< Connection: keep-alive
<
* Connection #0 to host myserver left intact
Welcome, new visitor!

Because this command was the first access since cookies were implemented on the server, the client did not have any cookies to include in the request. As expected, the server did not identify the cookie in the request and therefore included the cookie in the response headers, as indicated in the Set-Cookie: known=1; Path=/ line of the output. Since we have enabled cookies in curl, a new request will include the cookie known=1 in the request headers, allowing the server to identify the cookie’s presence:

$ curl http://myserver:8080/ -c cookies.txt -b cookies.txt -v
* Trying 192.168.1.225:8080...
* TCP_NODELAY set
* Connected to myserver (192.168.1.225) port 8080 (#0)
> GET / HTTP/1.1
> Host: myserver:8080
> User-Agent: curl/7.68.0
>Accept: /
> Cookie: known=1
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: text/plain; charset=utf-8
< Content-Length: 21
< ETag: W/"15-ATq2flQYtLMYIUpJwwpb5SjV9Ww"
< Date: Sat, 03 Jul 2021 23:45:47 GMT
< Connection: keep-alive
<
* Connection #0 to host myserver left intact
Welcome back, visitor

Cookie Security

The developer should be aware of potential vulnerabilities when using cookies to identify clients making requests. Attackers can use techniques such as cross-site scripting (XSS) and cross-site request forgery (CSRF) to steal cookies from a client and thereby impersonate them when making a request to the server. Generally speaking, these types of attacks use non-validated comment fields or meticulously constructed URLs to insert malicious JavaScript code into the page. When executed by an authentic client, this code can copy valid cookies and store them or forward them to another destination.

Therefore, especially in professional applications, it is important to install and use more specialized Express features, known as middleware. The express-session or cookie-session module provide more complete and secure control over session and cookie management. These components enable extra controls to prevent cookies from being diverted from their original issuer.

Guided Exercises

  1. How can the content of the comment field, sent within a query string of the HTTP GET method, be read in a handler function?

  2. Write a route that uses the HTTP GET method and the /agent path to send back to the client the contents of the user-agent header.

  3. Express.js has a feature called route parameters, where a path such as /user/:name can be used to receive the name parameter sent by the client. How can the name parameter be accessed within the handler function of the route?

Explorational Exercises

  1. If a server’s host name is myserver, which Express route would receive the submission in the form below?

    <form action="/contact/feedback" method="post"> ... </form>
  2. During server development, the programmer is not able to read the req.body property, even after verifying that the client is correctly sending the content via the HTTP POST method. What is a likely cause for this problem?

  3. What happens when the server has a route set to the path /user/:name and the client makes a request to /user/?

Summary

This lesson explains how to write Express scripts to receive and handle HTTP requests. Express uses the concept of routes to define the resources available to clients, which gives you great flexibility to build servers for any kind of web application. This lesson goes through the following concepts and procedures:

  • Routes that use the HTTP GET and HTTP POST methods.

  • How form data is stored in the request object.

  • How to use route parameters.

  • Customizing response headers.

  • Basic cookie management.

Answers to Guided Exercises

  1. How can the content of the comment field, sent within a query string of the HTTP GET method, be read in a handler function?

    The comment field is available in the req.query.comment property.

  2. Write a route that uses the HTTP GET method and the /agent path to send back to the client the contents of the user-agent header.

    app.get('/agent', (req, res) => {
      res.send(req.headers.user-agent)
    })
  3. Express.js has a feature called route parameters, where a path such as /user/:name can be used to receive the name parameter sent by the client. How can the name parameter be accessed within the handler function of the route?

    The name parameter is accessible in the req.params.name property.

Answers to Explorational Exercises

  1. If a server’s host name is myserver, which Express route would receive the submission in the form below?

    <form action="/contact/feedback" method="post"> ... </form>
    app.post('/contact/feedback', (req, res) => {
      ...
    })
  2. During server development, the programmer is not able to read the req.body property, even after verifying that the client is correctly sending the content via the HTTP POST method. What is a likely cause for this problem?

    The programmer did not include the express.urlencoded module, which lets Express extract the body of a request.

  3. What happens when the server has a route set to the path /user/:name and the client makes a request to /user/?

    The server will issue a 404 Not Found response, because the route requires the :name parameter to be provided by the client.

Linux Professional Insitute Inc. All rights reserved. Visit the Learning Materials website: https://learning.lpi.org
This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

Next Lesson

035.2 NodeJS Express Basics (035.2 Lesson 2)

Read next lesson

Linux Professional Insitute Inc. All rights reserved. Visit the Learning Materials website: https://learning.lpi.org
This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

LPI is a non-profit organization.

© 2025 Linux Professional Institute (LPI) is the global certification standard and career support organization for open source professionals. With more than 250,000 certification holders, it's the world’s first and largest vendor-neutral Linux and open source certification body. LPI has certified professionals in over 180 countries, delivers exams in multiple languages, and has hundreds of training partners.

Our purpose is to enable economic and creative opportunities for everybody by making open source knowledge and skills certification universally accessible.

  • LinkedIn
  • flogo-RGB-HEX-Blk-58 Facebook
  • Twitter
  • Contact Us
  • Privacy and Cookie Policy

Spot a mistake or want to help improve this page? Please let us know.

© 1999–2025 The Linux Professional Institute Inc. All rights reserved.