Express.js
Express is a minimalist web framework for Node.js. Express makes it easy to create server-side web applications by providing an easy way to read and manipulate the HTTP requests and responses.
Responding with a string
Responding to a client request is often just a matter of using the response functions available on the res
parameter. res
is a Node.js response object that is provided through Express. The following is a simple web server that only responds by send
ing the string "Hello world"
when a GET
request is made to the root of the application. It listens for connections on port 3000.
// server.tsimport express from 'express'const port = 3000const server = express()server.get('/', (req, res) => {res.send('<h1>Hello world</h1>')})server.listen(port, () => {console.log('The server is listening on port', port)})
To see this in action, start the server by running tsx watch server/sever.ts
or npm run dev
in your terminal, and visit http://localhost:3000 in your browser. You can also run curl http://localhost:3000
in another terminal tab or window.
The server.get()
call allows us to define endpoints, also called routes, such as /
. The server will only to respond to routes our application defines. Attempts to reach any other endpoint will cause Express to send a 404 File not found
response. If you start the server and navigate to http://localhost:3000/foo
in your browser, you should see Cannot GET /foo
.
Separation of concerns
In order to make testing our Express routes easier (which we'll cover later), we put our routes in a separate file to where we spin up our server to listen on port 3000
.
/** server.ts*/import express from 'express'const server = express()server.get('/', (req, res) => {res.send('<h1>Hello world</h1>')})export default server/** index.ts*/import server from './server.ts'const port = 3000server.listen(port, function () => {console.log('The server is listening on port', port)})
Responding with a file
Rather than responding to a request with an HTML string, we may have an HTML file prepared to send. We can easily send that file using the sendFile
method.
server.get('/home', (req, res) => {res.sendFile(Path.resolve('./index.html'))})
In the route, Path.resolve('./index.html')
resolves the relative path of the file from the root of the directory to an absolute address. Path
can be imported with import * as Path from 'path'
Accepting query parameters
One of the ways we can send data from the client to the server is to send name/value pairs in the URL, for example http://localhost:3000?name=value
.
The req
parameter in our Express routes is an object that contains information from the client about the request. The query above would be available on the query
property of the req
object.
// The req object{query: {name: 'value'},...}
Our application can then retrieve the values from the req.query
object. The following example sends a value it has received back in the response.
server.get('/webapps', (req, res) => {const personFromQuery = req.query.personres.send(personFromQuery + ' is building web apps')})
You can see this route work by starting the server and hitting http://localhost:3000/webapps?person=Robin
from your browser.
Route parameters
Another way we can send data to the server from the client is by using route parameters. For example on GitHub when you go to http://github.com/[org-name]/[repo-name] the GitHub server is looking at those parts of the URL to determine which data to show you.
We denote what the route parameters will be with a :
in our route definition. They will then be available on the params
property of the req
object.
server.get('/:org/:repo', (req, res) => {res.send('Org: ' + req.params.org + ', Repo: ' + req.params.repo)})// GET http://localhost:3000/dev-academy-challenges/learning-express// Responds with:// 'Org: dev-academy-challenges, Repo: learning-express'
Express is capable of much more than we're showing here, but this is enough to get us started. We'll continue to explore more of what Express has to offer, such as static files, view template rendering, and its middleware pipeline, in the near future.
Exposing web API routes
Routes are exposed for web APIs in the same way they are for routes that return other web assets. The main difference is they accept and respond with JSON.
There are a few different things you should notice about the code samples below. The endpoints they define use an :id
syntax and req.params
rather than req.query
. Using route params is more explicit and much more common with REST web APIs.
Returning JSON
We use res.json
to return JSON to the caller:
// server.tsserver.get('/:id', (req, res) => {const id = req.params.idconst widgetObj = {id: id,name: 'Best widget'}res.json(widgetObj)})
Notice how it just uses a normal JavaScript object. The .json()
method will take care of serialising the object to JSON.
Don't forget Express is also capable of receiving JSON in the request body. This is common when using the POST and PUT verbs to insert and update resources.
// server.tsimport express from 'express'const server = express()server.use(express.json())server.post('/widget', (req, res) => {const newWidget = req.body// ...})