Skip to content
UNASPACE

Express.js is a small web framework for Node.js.

It is mainly used to build:

  • web servers
  • REST APIs
  • server-rendered websites


Before building a backend, it helps to know the main concepts you usually find in one.



For this course, a simple setup is:

Terminal window
npm install express

server.js
import express from 'express';
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello from Express');
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
Diagram

Static files are files the server sends directly, such as:

  • images
  • CSS files
  • JavaScript files

In Express, you can serve them with express.static().

server.js
app.use(express.static('public'));

If your project has this file:

  • public/logo.png

Then the browser can request:

  • /logo.png

If your project has this file:

  • public/uploads/avatar.jpg

Then the browser can request:

  • /uploads/avatar.jpg

This is useful for things like website images, CSS, and frontend JavaScript files.


Routing means deciding which code runs for a request.

A route is based on:

  • the HTTP method
  • the path

Examples:

server.js
app.get('/users', (req, res) => {
res.json([{ id: 1, name: 'Alice' }]);
});
app.get('/users/:id', (req, res) => {
res.json({ id: req.params.id, name: 'Alice' });
});
app.post('/users', (req, res) => {
res.status(201).json({ message: 'User created' });
});
app.put('/users/:id', (req, res) => {
res.json({ message: `User ${req.params.id} updated` });
});
app.delete('/users/:id', (req, res) => {
res.json({ message: `User ${req.params.id} deleted` });
});

Use route params for one specific item.

server.js
app.get('/users/:id', (req, res) => {
res.json({ id: req.params.id });
});

Request:

GET /users/42


Use query params for filtering or searching.

server.js
app.get('/users', (req, res) => {
const { role } = req.query;
res.json({ role });
});

Request:

GET /users?role=admin


Routers help split large apps into smaller files.

routes/users.js
import express from 'express';
const router = express.Router();
router.get('/', (req, res) => {
res.send('All users');
});
export default router;

Mount the router in the main app:

server.js
app.use('/users', router);

Every route handler receives:

  • req for the incoming request
  • res for the outgoing response

Example:

server.js
app.get('/info', (req, res) => {
res.json({
method: req.method,
path: req.path,
headers: req.headers,
});
});

You will often read data from req and send data back with res.


Status codes tell the client what happened.

HTTP status codes are grouped like this:

  • 1xx informational
  • 2xx success
  • 3xx redirect
  • 4xx client error
  • 5xx server error

Common examples:

server.js
res.status(200).json({ ok: true });
res.status(201).json({ created: true });
res.status(204).send();
res.status(400).json({ error: 'Invalid data' });
res.status(401).json({ error: 'Unauthorized' });
res.status(403).json({ error: 'Forbidden' });
res.status(404).json({ error: 'Not found' });
res.status(500).json({ error: 'Internal Server Error' });

Middleware is a function that runs between request and response.

Middleware can:

  • run code
  • change req or res
  • stop the request
  • call next() to continue

Authentication is often checked with middleware before a protected route runs.

middlewares/auth.js
export function authCheck(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}
server.js
import { authCheck } from './middlewares/auth.js';
app.get('/profile', authCheck, (req, res) => {
res.json({ message: 'Private profile data' });
});

Errors should not crash the app without a clear response.

server.js
app.get('/error', (req, res) => {
throw new Error('Something went wrong');
});

Error middleware has 4 parameters:

server.js
app.use((err, req, res, next) => {
console.error(err.message);
res.status(500).json({ error: 'Internal Server Error' });
});

Express does not include a database by itself.

You connect a database by installing a Node.js driver.

For MySQL, a common package is:

Terminal window
npm install mysql2
db.js
import mysql from 'mysql2/promise';
export const pool = mysql.createPool({
host: process.env.DB_HOST || '',
user: process.env.DB_USER || '',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || '',
});
server.js
import { pool } from './db.js';
app.get('/users', async (req, res, next) => {
try {
const [rows] = await pool.query('SELECT id, name, email FROM users');
res.json(rows);
} catch (err) {
next(err);
}
});
server.js
import { pool } from './db.js';
app.get('/users/:id', async (req, res, next) => {
try {
const [rows] = await pool.query(
'SELECT id, name, email FROM users WHERE id = ?',
[req.params.id],
);
res.json(rows[0]);
} catch (err) {
next(err);
}
});

Template engines let the server generate HTML pages with data.

For this subject, use EJS.

Install it when you need server-rendered pages:

Terminal window
npm install ejs

Setup:

server.js
app.set('view engine', 'ejs');
app.set('views', './views');

Route:

server.js
app.get('/', (req, res) => {
res.render('index', {
title: 'Users',
users: ['Alice', 'Bob', 'Charlie'],
});
});

Example views/index.ejs:

views/index.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= title %></h1>
<ul>
<% users.forEach((user) => { %>
<li><%= user %></li>
<% }) %>
</ul>
</body>
</html>

This is useful when you want to loop over data and render HTML for each item.


For file uploads, a common package is:

Terminal window
npm install multer

Simple example:

server.js
import multer from 'multer';
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('avatar'), (req, res) => {
res.json({ filename: req.file.filename });
});

Debugging means finding out what the server is doing.

Simple ways:

  • use console.log()
  • test routes with Postman or the browser
  • use the Express debug logs

Run Express with debug output:

Terminal window
DEBUG=express:* node server.js

This shows useful internal logs for:

  • routes
  • middleware
  • rendering

Built with passion by Ngineer Lab