4. Intro to Express
Follow along with code examples here!
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:
What is Express and what problem does it solve compared to
node:http?What is an endpoint? What is a controller?
How do you define an endpoint and attach a controller using Express?
What are query strings? How do you access them inside a controller?
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:httpand removes repetitive boilerplate.Express
app— the object returned by callingexpress(). Used to register endpoints, middleware, and start the server.Endpoint — a specific URL path clients can send requests to. For example:
/api/usersor/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 viareq.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.paramsobject.
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.
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
node:http to ExpressWhat 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
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.
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:
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:
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.
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—thekey=valuepairs after the?in a URLreq.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
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.
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