Setup & App
npm install expressInstall Express.jsnpm install express --saveInstall and save to package.json dependenciesconst express = require('express')Import Express (CommonJS)import express from 'express'Import Express (ES Modules)const app = express()Create an Express application instanceapp.listen(3000)Start server on port 3000app.listen(3000, () => console.log('Running'))Start server with startup callbackapp.listen(port, host, callback)Start server with port, host, and callbackconst server = app.listen(3000)Store server reference for graceful shutdownapp.set('view engine', 'ejs')Set template engine to EJSapp.set('views', path.join(__dirname, 'views'))Set views directoryapp.set('trust proxy', 1)Trust first proxy (needed behind Nginx/load balancer)app.get('env')Get current environment (NODE_ENV or "development")app.locals.title = 'My App'Set app-level local variables for templatesprocess.env.NODE_ENVAccess current environment (development/production)app.disable('x-powered-by')Remove X-Powered-By header for securityapp.enable('strict routing')Make /foo and /foo/ different routesapp.enable('case sensitive routing')Make /Foo and /foo different routesRouting
app.get('/path', handler)Handle GET requestapp.post('/path', handler)Handle POST requestapp.put('/path', handler)Handle PUT requestapp.patch('/path', handler)Handle PATCH requestapp.delete('/path', handler)Handle DELETE requestapp.all('/path', handler)Handle all HTTP methodsapp.use('/path', handler)Mount middleware/router at path (matches prefix)app.get('/users/:id', handler)Route with named parameterapp.get('/users/:id?', handler)Route with optional parameterapp.get('/files/*splat', handler)Wildcard route - v5 uses named wildcard (*name); v4 uses bare *app.get('/path', mw1, mw2, handler)Multiple handlers for a single routeapp.get('/path', [mw1, mw2], handler)Array of handlers for a single routeconst router = express.Router()Create a modular routerrouter.get('/', handler)Define route on routerapp.use('/api', router)Mount router at /api prefixrouter.route('/users').get(h).post(h)Chainable route handlersrouter.route('/users/:id').get(h).put(h).delete(h)Chained handlers for resource CRUDexpress.Router({ mergeParams: true })Access parent route params in child routerexpress.Router({ strict: true })Strict mode: /foo and /foo/ are differentRequest Object (req)
req.params.idNamed route parameter valuereq.paramsObject of all route parametersreq.query.nameQuery string parameter (?name=value)req.queryObject of all query string parametersreq.bodyParsed request body (requires body-parser middleware)req.body.emailAccess specific field from parsed bodyreq.headers['content-type']Access request header by name (lowercase)req.header('Authorization')Get header value (case-insensitive)req.get('Accept')Alias for req.header()req.methodHTTP method (GET, POST, etc.)req.pathURL path without query stringreq.urlFull URL including query string (relative to mount point)req.originalUrlFull original URL (not affected by mounting)req.hostnameHostname from Host header (or X-Forwarded-Host with trust proxy)req.ipClient IP addressreq.ipsArray of IPs from X-Forwarded-For (requires trust proxy)req.protocolProtocol: "http" or "https"req.securetrue if connection is HTTPSreq.cookiesParsed cookies object (requires cookie-parser)req.signedCookiesSigned cookies object (requires cookie-parser with secret)req.is('application/json')Check if request Content-Type matchesreq.accepts('json')Check if client accepts given type (from Accept header)req.freshtrue if response is still fresh (cache related)req.staletrue if response is stale (opposite of fresh)req.subdomainsArray of subdomains in the domain of the requestreq.xhrtrue if X-Requested-With is XMLHttpRequestResponse Object (res)
res.send('Hello World')Send a string responseres.send(Buffer)Send a Buffer as binary responseres.json({ key: 'value' })Send JSON response (sets Content-Type automatically)res.status(404).json({ error: 'Not found' })Set status code and send JSONres.sendStatus(204)Send status code with default status message bodyres.redirect('/new-url')Redirect to URL (302 by default)res.redirect(301, '/new-url')Permanent redirectres.redirect('back')Redirect to Referer header URLres.render('template', { data })Render view template with datares.sendFile(path)Send file at absolute pathres.sendFile(path, options, callback)Send file with options and completion callbackres.download(path, filename)Prompt download with optional custom filenameres.set('Content-Type', 'text/html')Set single response headerres.set({ 'X-Custom': 'value' })Set multiple response headers at onceres.get('Content-Type')Get current response header valueres.append('Link', '<url>')Append value to response headerres.cookie('name', value, options)Set a cookieres.clearCookie('name')Clear a cookieres.type('json')Set Content-Type by extension or MIME typeres.format({ 'text/html': fn, 'application/json': fn })Content negotiation - respond based on Accept headerres.locals.user = req.userPass data to template via response localsres.end()End response without datares.headersSentBoolean - true if headers have already been sentres.vary('Accept')Add field to Vary headerres.links({ next: url, prev: url })Set Link header for paginationMiddleware
app.use(middleware)Apply middleware to all routesapp.use('/path', middleware)Apply middleware to specific path prefix(req, res, next) => {}Middleware function signaturenext()Pass control to next middleware or route handlernext(err)Pass error to error-handling middlewarenext('route')Skip remaining handlers in current route stacknext('router')Skip remaining middleware in this Router and continue in the calling layerexpress.json()Parse JSON request bodies (built-in)express.json({ limit: '10mb' })Parse JSON with body size limitexpress.urlencoded({ extended: true })Parse URL-encoded form bodies (built-in)express.urlencoded({ extended: false })Parse URL-encoded using querystring (no nested objects)express.static('public')Serve static files from "public" directoryexpress.static('public', { maxAge: '1d' })Serve static files with cache controlexpress.raw()Parse raw binary request bodies as Bufferexpress.text()Parse text/plain request bodies as stringapp.use(cors())Enable CORS for all routes (requires cors package)app.use(helmet())Set security headers (requires helmet package)app.use(morgan('dev'))HTTP request logger (requires morgan package)app.use(compression())Gzip compress responses (requires compression package)Error Handling
(err, req, res, next) => {}Error-handling middleware signature (4 params required)app.use((err, req, res, next) => { ... })Register error handler (must be after all other app.use())next(new Error('Something failed'))Pass error to error-handling middlewareerr.status / err.statusCodeError HTTP status code (Express uses err.status)err.messageError message stringres.status(err.status || 500).json({ error: err.message })Common error response patternapp.use((req, res) => res.status(404).send('Not found'))Catch-all 404 handler (register last)class AppError extends Error { constructor(msg, status) {...} }Custom error class with status codethrow new Error('msg')Synchronous errors caught automatically in route handlersasync (req, res, next) => { try { ... } catch(e) { next(e) } }In Express v4, async errors must be caught manually and forwarded with next(err)require('express-async-errors')Auto-wrap async handlers to forward errors (third-party)throw new Error('msg') // async, v5Express v5 auto-catches async throws - no try/catch neededprocess.on('unhandledRejection', (err) => { ... })Catch unhandled promise rejections globallyprocess.on('uncaughtException', (err) => { ... })Catch uncaught synchronous exceptions globallyRouter
const router = express.Router()Create a modular router instancerouter.use(middleware)Apply middleware to all router routesrouter.get('/', handler)GET route on routerrouter.post('/', handler)POST route on routerrouter.param('id', callback)Pre-processing callback for route paramrouter.param('id', (req, res, next, id) => {})Load resource by ID before handler runsrouter.route('/items').get(listAll).post(create)Chained route handlers on same pathrouter.route('/items/:id').get(getOne).put(update).delete(remove)Full CRUD on parameterized routeapp.use('/api/v1', router)Mount router at versioned API prefixexpress.Router({ mergeParams: true })Inherit params from parent routermodule.exports = routerExport router for use in main app fileconst userRoutes = require('./routes/users')Import route moduleapp.use('/users', userRoutes)Mount imported user routesStatic Files & Templates
app.use(express.static('public'))Serve files from public/ directoryapp.use('/static', express.static('assets'))Serve assets/ at /static URL prefixexpress.static('public', { dotfiles: 'ignore' })Ignore dotfiles in static servingexpress.static('public', { etag: false })Disable ETag generationexpress.static('public', { extensions: ['html'] })Try .html extension if file not foundexpress.static('public', { index: 'index.html' })Specify default index fileexpress.static('public', { maxAge: '7d' })Set cache-control max-ageexpress.static('public', { redirect: false })Disable trailing slash redirect for directoriesapp.set('view engine', 'ejs')Set EJS as template engineapp.set('view engine', 'pug')Set Pug as template engineapp.set('view engine', 'hbs')Set Handlebars as template engineapp.set('views', './views')Set views directoryapp.set('views', ['./views', './partials'])Multiple view directoriesres.render('index')Render views/index.ejs (or .pug etc.)res.render('index', { title: 'Home', user })Render template with data objectres.render('index', (err, html) => res.send(html))Render with callback instead of auto-sendCommon Middleware (Third-Party)
cors()Enable CORS with default settings (allow all origins)cors({ origin: 'https://example.com' })Restrict CORS to specific origincors({ origin: ['https://a.com', 'https://b.com'] })Allow multiple originscors({ credentials: true })Allow cookies with CORS requestshelmet()Set secure HTTP headers (XSS, HSTS, etc.)helmet({ contentSecurityPolicy: false })Helmet with CSP disabledmorgan('dev')Concise colored HTTP request loggingmorgan('combined')Apache-style combined HTTP request loggingcompression()Gzip/deflate response compressionrateLimit({ windowMs: 15*60*1000, max: 100 })Rate limit: 100 req per 15 minutesupload.single('file')Multer: accept single file upload (field name "file")upload.array('photos', 5)Multer: accept up to 5 files from "photos" fieldupload.fields([{ name: 'avatar' }, { name: 'docs' }])Multer: accept files from multiple fieldsbody('email').isEmail()express-validator: validate email fieldvalidationResult(req)express-validator: extract validation errors from requestcookieParser(secret)Parse cookies and optionally sign themsession({ secret: 'key', resave: false, saveUninitialized: false })express-session: create session middlewarereq.session.userId = user.idStore data in sessionreq.session.destroy()Destroy session (logout)Patterns & Best Practices
async (req, res, next) => { try { ... } catch(e) { next(e) } }Async/await error forwarding patternconst wrap = fn => (req, res, next) => fn(req, res, next).catch(next)Async handler wrapper utilityapp.use('/v1', v1Router); app.use('/v2', v2Router)API versioning with separate routersrequire('dotenv').config()Load .env file for environment variablesexpress.json({ limit: '10mb' })Set request body size limitapp.disable('x-powered-by')Hide Express fingerprint from response headersrouter.param('userId', loadUser)Load resources by ID with param middleware// app.js - express config; server.js - app.listen()Separate app config from server startup for testabilitymodule.exports = appExport app without listening (for testing and imports)process.on('SIGTERM', () => server.close())Graceful shutdown on SIGTERM signalprocess.on('SIGINT', () => server.close())Graceful shutdown on Ctrl+C (SIGINT)// controllers/userController.jsController pattern: separate route logic from route definitions// services/userService.jsService layer: business logic separate from HTTP handlingres.status(201).json({ id: newItem.id })Return correct status codes (201 Created, etc.)app.use(express.json()); app.use(express.urlencoded({ extended: false }))Register body parsers before route handlersif (process.env.NODE_ENV !== 'production') { app.use(morgan('dev')) }Conditional middleware based on environmentconst PORT = process.env.PORT || 3000Use environment variable for port with fallbackTesting
npm install --save-dev supertestInstall supertest for HTTP integration testingconst request = require('supertest')Import supertestrequest(app).get('/').expect(200)Assert 200 status on GET /request(app).get('/').expect('Content-Type', /json/)Assert response Content-Type matches regexrequest(app).post('/users').send({ name: 'Alice' }).expect(201)POST with body and assert 201 Created.set('Authorization', 'Bearer token')Set request header in supertest chain.query({ page: 1, limit: 10 })Set query string parameters in supertest.then(res => { expect(res.body.name).toBe('Alice') })Assert on response body propertiesconst res = await request(app).get('/users')Async/await style supertest requestexpect(res.statusCode).toBe(200)Jest assertion on status codeexpect(res.body).toHaveProperty('id')Jest assertion on body propertybeforeAll(async () => { await db.connect() })Set up database before all testsafterAll(async () => { await db.close() })Clean up database after all testsjest.mock('./services/emailService')Mock a module for isolated unit testing