Your First Express Server: Routes, Requests and Responses

Node.js and Express: Build a Back End API from Scratch · Express · 5 min read · Updated

Express answers a request

  1. A request arrives, for example GET /api/health.
  2. Express looks for a route that matches the method and the path.
  3. It calls your handler function and gives it the request and response objects.
  4. Your handler sends one response, for example with res.json.

Express is the most used web framework for Node.js. It is small, it has been around for many years and a huge amount of documentation and examples exist for it. Express does not change how Node works. It adds a clean way to describe which code answers which request. In this lesson you will build the same kind of server as in lesson 5, but with far less code.

Install and start

Inside your taskapi folder, you already ran npm install express in lesson 2. If not, run it now. Create a file server.js with this code. This course uses Express 5, which is the current major version. Most of what you learn also applies to Express 4, and we point out the differences where they matter.

import express from 'express';

const app = express();

app.get('/', (req, res) => {
  res.send('Hello from Express');
});

app.get('/api/health', (req, res) => {
  res.json({ ok: true, time: new Date().toISOString() });
});

const PORT = 3000;
app.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`));

Start it with node server.js, or better, with npm run dev after you have added the scripts from lesson 2. Then test it.

curl http://localhost:3000/
curl http://localhost:3000/api/health
Hello from Express
{"ok":true,"time":"2026-10-28T04:00:00.000Z"}

The time you see will be your current time. Now look at the code in four parts.

  • express() creates the app, an object that holds your routes and settings.
  • app.get(path, handler) says: when a GET request arrives for this path, run this function. There are matching methods app.post, app.patch, app.put and app.delete.
  • The handler receives req (the request) and res (the response).
  • app.listen(port, callback) starts the server. The callback runs once it is ready.

The request object, req

The request object describes what the client sent. You will use these parts all the time.

  • req.method and req.originalUrl: the method and the full URL path with the query string.
  • req.params: values taken from the path, such as the 7 in /api/tasks/7. Lesson 7 covers this.
  • req.query: values after the question mark, such as ?done=true.
  • req.body: the data sent in the body of a POST or PATCH. It needs a body parser, which lesson 8 covers.
  • req.get('Authorization'): reads one request header.
  • req.ip: the address of the client.

The response object, res

You must send exactly one response for each request. If you never send one, the client waits until it times out. If you send two, Node throws an error. These are the methods you will use.

  • res.send('text') sends text, HTML or a buffer, and sets the content type for you.
  • res.json(object) converts a JavaScript object into JSON and sets Content-Type: application/json. This is the main method for APIs.
  • res.status(404) sets the status code and returns res, so you can chain: res.status(404).json({ error: 'Not found' }).
  • res.status(204).end() sends an empty response, used after a delete.
  • res.set('Header-Name', 'value') sets a header.
  • res.redirect('/other') sends the client to another URL.

Restart automatically while you work

Node reads your file once at start-up. If you change the code, the old version keeps running until you restart. Instead of stopping and starting by hand, run node --watch server.js. Node watches the files it loaded and restarts whenever one changes. That is what the dev script from lesson 2 does.

Ports and the PORT variable

We used the fixed port 3000. On a real server, you often cannot choose the port yourself, because a hosting service or a process manager gives it to you through an environment variable called PORT. Lesson 11 shows how to read it with a fallback, so the same code runs on your laptop and on a server.

Express 5 handles async errors

In Express 4, if an async handler threw an error or rejected a promise, the error was not caught and you had to wrap every handler by hand. Express 5 catches rejected promises from handlers and passes them to the error handler for you. That makes async handlers much simpler, and you will rely on it from lesson 9.

Common problems

  • `Cannot GET /something`: no route matches. Check the path and the method. A POST route does not answer a GET request.
  • The browser shows old content: your code did not restart. Use --watch.
  • `EADDRINUSE`: the port is already used by another copy of the server. Stop it.
  • `Error [ERR_MODULE_NOT_FOUND]`: you forgot .js in a file import, or the package is not installed.

Your task

  1. Create server.js with the code above and start it with node --watch server.js.
  2. Add a route GET /api/hello/world that returns { "message": "Hello, world" }.
  3. Change the text of the home page while the server runs and refresh. Check that the change appears.
  4. Use curl -i on /api/health and find the Content-Type header.

How to check it worked

  • The browser shows Hello from Express at http://localhost:3000/.
  • /api/health returns JSON with ok set to true.
  • After saving a change, the terminal shows that the server restarted and the new text appears.

Frequently asked questions

What is the difference between res.send and res.json?

res.send sends text, HTML or a buffer. res.json converts an object to JSON and sets the JSON content type. Use res.json for APIs.

Why does my browser show the old version after I edit the code?

Node does not reload code by itself. Restart the server, or start it with node --watch so it restarts when files change.