Logo Sujal Magar
ExpressJS Fundamentals

ExpressJS Fundamentals

January 20, 2026
6 min read
Table of Contents

ExpressJS

ExpressJS is a popular web framework for Node.js that makes it easier to build web applications and APIs. It’s lightweight, flexible, and handles things like routing, HTTP requests, and responses. To get started, install it from npm or yarn.

npm install express
# or
yarn add express

Create a basic server

// main.js
import express from 'express'
 
const PORT = 3000
 
const app = express()
 
app.get('/', (req, res) => {
  res.send('Hello from the ExpressJS server')
})
 
app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`)
})

Run it with node main.js. This sets up a simple server that responds with “Hello from the ExpressJS server” when you visit http://localhost:3000 in your browser.

Add one more route

app.get('/about', (req, res) => {
  res.send('About use page')
})
 
app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`)
})

Now try visiting http://localhost:3000/about

Return JSON (most common for APIs)

// parse JSON bodies to json format
// Important for POST/PUT requests
app.use(express.json())
 
app.get('/api/users', (req, res) => {
  res.json([
    { id: 1, name: 'John', role: 'user' },
    { id: 2, name: 'Doe', role: 'admin' },
  ])
})
 
app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`)
})

Middleware in ExpressJS

Middleware are functions that run between the request and response They can modify requests/responses, end the cycle, or pass control to the next middleware. Think of them as filters or plugins.

  • Build-in middleware: Express has some like express.json() for parsing JSON bodies.
app.use(express.json())
  • Custom middleware: A simple logger middleware in middleware/logger.js.
// middleware/logger.js
export const logger = (req, res, next) => {
  console.log(`${req.method} | ${req.url}`)
 
  next() // Pass to next middleware or route
}
 
// main.js
import { logger } from './middleware/logger'
 
app.use(logger) // Applies to all routes
  • Third-party: Like cors for cross-origin requests (npm install cors or yarn add cors)
import cors from 'cors'
 
app.use(cors())

Middleware runs in order, so place global ones (like logging) at the top.


Validation in ExpressJS

Validation checks if user input (e.g., form data) is correct before processing. It prevents bad data from causing errors. Use libraries like zod or express-validator.

// Define the schema
const userSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
})
 
// Validation middleware
function validateBody(schema) {
  return (req, res, next) => {
    try {
      // .parse() → throws on error
      req.validated = schema.parse(req.body)
      next()
    } catch (error) {
      // Zod gives very good error messages
      return res.status(400).json({
        error: 'Validation failed',
        issues: error.errors, // ← most useful part
        // received: req.body           // optional – good for debugging
      })
    }
  }
}
 
app.post('/users', validateBody(userSchema), (req, res) => {
  // req.validated is already typed & safe
  const user = req.validated
 
  // Handle data here
 
  res.status(201).json({
    message: 'User created',
    user,
  })
})

This checks the request body and returns errors if invalid.

Error Handling

Error handling catches and responds to issues like database failures or invalid routes. Express has a default handler, but customize it for better control.

Basic error middleware

Add at the end of your app (after all routes).

app.use((error, req, res, next) => {
  console.error(error.stack)
  res.status(500).send('Something went wrong!')
})

Throwing errors

In routes or controllers

app.get('/error', (req, res, next) => {
  next(new Error('Test error')) // Passes to error handler
})

Async errors

For promises, use a wrapper like below:

const asyncHandler = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next)
 
app.get(
  '/async',
  asyncHandler(async (req, res) => {
    throw new Error('Async error')
  }),
)

This ensures errors are caught and handled gracefully.


Logging in ExpressJS

Logging records what happens in your app (e.g., requests, errors) for debugging and monitoring. Use libraries like pino, and pino-http.

// logger.js
import pinoLogger from 'pino'
import pinoHttp from 'pino-http'
 
export const logger =
  process.env['ENVIRONMENT'] === 'dev'
    ? pinoLogger({
        level: process.env['PINO_LOG_LEVEL'],
        transport: {
          target: 'pino-pretty',
          options: {
            colorize: true,
          },
        },
      })
    : pinoLogger({
        level: process.env['PINO_LOG_LEVEL'],
      })
 
export const loggerMiddleware = pinoHttp({
  logger,
})
 
// main.js
import { loggerMiddleware } from './logger.js'
 
app.use(loggerMiddleware)

Status Codes in ExpressJS

HTTP status codes tell the client what happended with the request (e.g., success, error). Express uses res.status(code) to set them.

Some status codes are listed below:

CodeMeaningExample UseCommand in Express
200OK (Success)Request succeededres.status(200).send("OK");
201CreatedNew resource createdres.status(201).json(newItem);
400Bad RequestInvalid inputres.status(400).send("Bad data");
401UnauthorizedAuthentication neededres.status(401).send("Login required");
403ForbiddenAccess deniedres.status(403).send("No premission");
404Not FoundResource missingres.status(404).send("Not found");
500Internal Server ErrorServer-side errorres.status(500).send("Server error");

Tip: Always pair with a response body for clarity.


Best Practices for Security

Security protects your app from attacks. Some protections we can use are listed below:

XSS (Cross-Site Scripting)

Prevents attackers from injecting malicious scripts into your page. It sanitize user input and use Content Security Policy (CSP).

Best practice: Use libraries like xss to filter input:

const xss = require('xss')
 
const cleanInput = xss(req.body.comment)

CSRF (Cross-Site Request Forgery)

Stops unauthoried commands from other sites. Use CSRF tokens.

Best practive: Use libraries like csurf for middleware:

const csurf = require('csurf')
 
app.use(csurf({ cookie: true }))

HSTS (HTTP Strict Transport Security)

Forces HTTPS to encrypt traffic.

Enable manually as:

app.use((req, res, next) => {
  res.setHeader(
    'Strict-Transport-Security',
    'max-age=31536000;includeSubDomains',
  )
 
  next()
})

Helmet

Or use helmet package which is a collection of middleware for security headers:

import helmet from 'helmet'
 
app.use(helmet())

Other best practices

  • Use HTTPS: With https modules or other services.
  • Rate limiting: Prevent DDoS with express-rate-limit.
  • Secure cookies: Set secure: true and httpOnly: true.
  • Keep dependencies updated: Run npm audit regularly.
  • Input validation/sanitization: Always check and clean user data.
  • Error handling: Don’t expose stack traces in production.

Practice by building a small app incorporating these.