4. Intro to Express

circle-info

Follow along with code examples herearrow-up-right!

In the last lesson, you built an HTTP server from scratch using Node's built-in node:http module. You manually matched routes with if/else, set headers on every response, called res.end() on every code path, and parsed query strings by hand. It worked — but it took a lot of boilerplate.

Express is a Node framework that wraps node:http and handles all of that boilerplate for you. In this lesson you'll see exactly what Express gives you, learn the core patterns you'll use in every server you build, and finish with a clear picture of what's coming next.

Table of Contents:

Essential Questions

By the end of this lesson, you should be able to answer these questions:

  1. What is Express and what problem does it solve compared to node:http?

  2. What is an endpoint? What is a controller?

  3. How do you define an endpoint and attach a controller using Express?

  4. What are query strings? How do you access them inside a controller?

  5. What are route parameters? How do you access them inside a controller?

Key Concepts

  • Express — the most popular Node framework for building HTTP server applications. It wraps node:http and removes repetitive boilerplate.

  • Express app — the object returned by calling express(). Used to register endpoints, middleware, and start the server.

  • Endpoint — a specific URL path clients can send requests to. For example: /api/users or /api/posts/:id.

  • Controller — a callback function invoked when a matching request arrives. It reads the request and sends a response.

  • req — the request object. Contains the URL, method, headers, query strings, and body of the incoming request.

  • res — the response object. Has methods for sending data back to the client.

  • next — a function that passes the request to the next handler in the chain. Used by middleware (covered in the next lesson).

  • query strings — key-value pairs appended to a URL after ? and separated by &, used to filter or customize a response. Accessible via req.query.

  • Route Parameters — named URL segments that are used to capture the values specified at their position in the URL. The captured values are populated in the req.params object.

Review: The Client-Server Model

You've already learned this pattern — but it's worth keeping in front of you as the foundation for everything in this module.

A server receives requests from a client and sends back responses. The server decides how to respond based on the URL and HTTP method of each request.

express diagram

The server we're building is the middle layer: it receives requests from the browser, does whatever work is needed (looking up data, calling an API, reading from a database), and sends a response back.

From node:http to Express

What Express Handles For You

Here is the same GET /api/todos route written with node:http and with Express:

res.send(todos) does the work of three lines: it sets the status to 200, sets the Content-Type header to application/json, and serializes the object to JSON — all at once.

More broadly, Express handles everything you had to do manually with node:http:

With node:http

With Express

if/else chains on req.method and req.url

app.get(), app.post(), etc.

res.writeHead() on every response

res.send() handles it

JSON.stringify() on every response body

res.send() handles it

res.end() on every code path

res.send() handles it

new URL(req.url) to parse query strings

req.query

Stream events to read the request body

express.json() middleware

Express doesn't add capabilities that node:http doesn't have — everything it does, you could write yourself. It just removes the repetition so you can focus on what your server actually does.

Installing and Creating the App

The main export of Express is the express function, which returns an app object used to configure everything:

app.listen() starts the server on the given port — the same role server.listen() played with node:http. During development, localhost (your own machine) is the host and 8080 is the port, so the server is reachable at http://localhost:8080.

Endpoints and Controllers

With node:http you routed requests using if/else chains. Express replaces that with dedicated methods for each HTTP verb:

An endpoint is the URL path — the string that tells Express which requests to match. A controller is the callback that runs when a matching request arrives.

When your server is running at http://localhost:8080, each endpoint becomes:

  • http://localhost:8080/

  • http://localhost:8080/api/hello

circle-info

It is a best practice to prefix data endpoints with /api/ to distinguish them from endpoints that serve HTML and other static content.

The Controller Signature

Controllers are invoked when the matching request arrives at the server. They are responsible for interpreting the incoming request and sending a response.

To do this job, every controller receives three arguments called req, res, and next:

  • req — the incoming request. Contains everything about what the client sent.

  • res — methods for building and sending the response.

  • next — a function that passes the request to the next handler if multiple handlers are used to process the request. Controllers typically don't use this — but middleware does. More on that in the next lesson.

circle-info

Write controllers as named functions rather than inline arrow functions. Named controllers are easier to read, test, and reuse across multiple endpoints.

Let's start by looking at sending responses with res. Then, we'll look at the req object. In the next lesson, we'll look closely at the next function.

Sending Responses

The most common way to send a response is with res.send(). With node:http, we would have to use res.writeHead() to set the status and specify the content type and res.end() to send data.

In express, res.send() always sends a 200 status, automatically sets the content type, and sends the given data:

Test this in your terminal with curl -i


Endpoint and Controller Challenge: Add an endpoint to your server for GET http://localhost:8080/api/users requests that sends the users array below as a response:

chevron-rightSolutionhashtag

Specifying Status

When we want to specify the status code, we have two main options.

res.sendStatus(code) sends a status code with no body. This is useful for simple health check endpoints:

circle-info

A "health check" endpoint can be used to quickly check to see if a server is running

res.status().send() sets the status code before sending the body. We can use this to write a "catch-all" fallback endpoint that sends a 404 when a request doesn't match any defined endpoints

Any request that doesn't match our defined endpoints will land here and get a clean JSON error.

circle-info

In the next lesson, once you add a full frontend, this catch-all will change. Instead of returning a JSON 404, it will serve index.html — so the browser's client-side router can handle unknown routes rather than the server.

The Request Object

The req parameter is an object containing information about the incoming request. It has a few key properties:

  • req.query: captures query strings—the key=value pairs after the ? in a URL

  • req.params: captures route parameters—named segments of the URL defined in the route path

Query Strings

Query strings are the parts of a URL that come after a ? and come in key=value pairs. They are often used by clients to filter or modify the data they are requesting.

In these examples, can you tell what the query strings are doing to the results?

In our server, let's make it so that our /api/hello endpoint can send accept query strings for a first and last name and send a custom message in response.

Express parses query strings automatically and makes them available in the req.query object

Test it with curl!

With a node:http server, reading query strings required manually constructing a URL object.


Query String Challenge: Modify serveUsers so that when a contains query string is provided, only users whose name matches are returned.

For example: /api/users?contains=a would return Carmen and Maya

chevron-rightSolutionhashtag

Route Parameters

If you completed the endpoints and controllers challenge, you will have a /api/users endpoint that sends the entire users array:

Suppose our client knows the id of the user they want to see and just wants that user's data. We can set up an endpoint to handle requests like GET /api/users/3 where the value 3 indicates the id of the user they want.

On the server, we can define an endpoint with a route parameter using the syntax :id, like so:

The findUser controller can access the provided id from the req.params object:

Route Parameters Challenge: It is possible that the client requests a user that doesn't exist. Add a guard clause to the findUser controller that sends a response with the body { message: No user with the given id } and a 404 status.

chevron-rightSolutionhashtag

Complete Code

What's Next: Middleware

Middleware is a function that runs for every incoming request before a controller sends a response. It uses next() to pass the request along the chain, and app.use() to register it globally:

Middleware is how Express handles serving static files, parsing request bodies, protecting routes, and much more. Next lesson you'll learn exactly how it works — and use it to serve an entire frontend application with a single line of code.

Last updated