Skip to content
Express.js Guide — Complete Web Framework for Node.js

Express.js Guide — Complete Web Framework for Node.js

DodaTech Updated Jun 6, 2026 6 min read

Express is a minimal, unopinionated web framework for Node.js — it provides a thin layer of routing, middleware, static file serving, and template rendering without obscuring Node.js features.

What You’ll Learn

By the end of this tutorial, you’ll build a RESTful API with Express, use middleware for logging and error handling, serve static files, structure a real-world application, and implement security best practices.

Why Express.js Matters

Express is the most popular Node.js framework — used by companies like IBM, Uber, and MySpace. At DodaTech, Doda Browser’s bookmark sync API and DodaZIP’s file conversion endpoints are built with Express. Its minimal design means you have complete control over your application architecture while benefiting from a vast ecosystem of middleware packages.

Express.js Learning Path

    flowchart LR
  A[Core Modules] --> B[Express.js]
  B --> C[Database]
  C --> D[Advanced]
  B --> E{You Are Here}
  style E fill:#f90,color:#fff
  
Prerequisites: https://tutorials.dodatech.com/backend/nodejs/nodejs-basics/ and https://tutorials.dodatech.com/backend/nodejs/nodejs-core-modules/. Understand HTTP methods (GET, POST, PUT, DELETE) and JavaScript callbacks.

What is Express? (The “Why” First)

Think of Express as the plumbing for your web application. The raw http module in Node.js is like having water pipes but no faucets — functional but not user-friendly. Express adds the faucets (routing), the water filters (middleware), and the fixtures (static file serving, template rendering) that make building web applications practical.

Setup — Your First Express Server

npm init -y
npm install express
npm install -D nodemon   # Auto-restart during development
// server.js
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;

app.get("/", (req, res) => {
  res.send("Hello Express!");
});

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`);
});
// package.json scripts
"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js"
}

Line by line:

  • express() — creates the application object
  • app.get("/", callback) — handles GET requests to /
  • res.send(...) — sends a response (auto-detects content type)
  • app.listen(port, callback) — starts the server

Routing — Mapping URLs to Code

// HTTP methods
app.get("/users", (req, res) => res.json({ users: [] }));
app.post("/users", (req, res) => res.status(201).json({ id: 1 }));
app.put("/users/:id", (req, res) => res.json({ id: req.params.id }));
app.delete("/users/:id", (req, res) => res.status(204).send());

// Route parameters
app.get("/users/:userId/books/:bookId", (req, res) => {
  res.json(req.params);  // { userId: "42", bookId: "5" }
});

// Query parameters
app.get("/search", (req, res) => {
  res.json(req.query);   // /search?q=express&page=2
});

// Express Router — modular routes
const router = express.Router();
router.get("/", (req, res) => res.json({ message: "Users list" }));
router.get("/:id", (req, res) => res.json({ id: req.params.id }));
app.use("/api/users", router);

Middleware — The Request Pipeline

Middleware functions run in sequence during the request-response cycle. Each can modify the request/response or end the request:

const app = express();

// Built-in middleware
app.use(express.json());                     // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse form data
app.use(express.static("public"));           // Serve static files

// Custom logging middleware
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
  next();  // Pass control to the next middleware
});

// Route-specific middleware
function requireAuth(req, res, next) {
  const token = req.headers.authorization;
  if (!token) return res.status(401).json({ error: "Unauthorized" });
  req.user = { id: 1, name: "Alice" };
  next();
}

app.get("/profile", requireAuth, (req, res) => {
  res.json({ user: req.user });
});

// Third-party middleware
const cors = require("cors");
const helmet = require("helmet");
const morgan = require("morgan");
app.use(helmet());
app.use(cors());
app.use(morgan("dev"));

Middleware order mattersexpress.json() must come before route handlers. Error handlers must be last.

Error Handling

// 404 handler — after all routes
app.use((req, res) => {
  res.status(404).json({ error: "Route not found" });
});

// Global error handler — 4 params
app.use((err, req, res, next) => {
  console.error("Error:", err.stack);
  res.status(err.status || 500).json({
    error: process.env.NODE_ENV === "production"
      ? "Internal server error"
      : err.message
  });
});

// Async error wrapper (Express 5 handles this natively)
function asyncHandler(fn) {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}

Express 4 doesn’t catch promise rejections automatically. The asyncHandler wrapper ensures async errors reach the error handler.

REST API Example — Complete CRUD

const express = require("express");
const app = express();
app.use(express.json());

let books = [
  { id: 1, title: "1984", author: "George Orwell", year: 1949 },
  { id: 2, title: "Brave New World", author: "Aldous Huxley", year: 1932 }
];
let nextId = 3;

app.get("/api/books", (req, res) => {
  let result = books;
  if (req.query.author) {
    result = books.filter(b =>
      b.author.toLowerCase().includes(req.query.author.toLowerCase())
    );
  }
  res.json(result);
});

app.get("/api/books/:id", (req, res) => {
  const book = books.find(b => b.id === Number(req.params.id));
  if (!book) return res.status(404).json({ error: "Not found" });
  res.json(book);
});

app.post("/api/books", (req, res) => {
  const { title, author, year } = req.body;
  if (!title || !author) return res.status(400).json({ error: "Title and author required" });
  const book = { id: nextId++, title, author, year };
  books.push(book);
  res.status(201).json(book);
});

app.put("/api/books/:id", (req, res) => {
  const book = books.find(b => b.id === Number(req.params.id));
  if (!book) return res.status(404).json({ error: "Not found" });
  Object.assign(book, req.body, { id: Number(req.params.id) });
  res.json(book);
});

app.delete("/api/books/:id", (req, res) => {
  const index = books.findIndex(b => b.id === Number(req.params.id));
  if (index === -1) return res.status(404).json({ error: "Not found" });
  books.splice(index, 1);
  res.status(204).send();
});

app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: "Internal server error" });
});

app.listen(3000);

Common Mistakes

1. Middleware orderexpress.json() must be before routes. Error handlers must be after all routes.

2. Forgetting next() — Middleware that doesn’t call next() hangs the request.

3. Not handling async errors — Express 4 doesn’t catch promise rejections. Use try/catch or asyncHandler.

4. Sending multiple responses — Always return early when sending error responses to prevent double-send.

5. Exposing stack traces in production — Check NODE_ENV before sending error details.

Practice Questions

1. What is middleware in Express?

Functions that execute during the request-response cycle — they can modify req/res, end the request, or call the next middleware.

2. How do you parse JSON request bodies?

Use express.json() middleware. It parses JSON payloads and makes them available as req.body.

3. Difference between app.use and app.get?

app.use applies to all HTTP methods. app.get applies only to GET requests. Both accept an optional path prefix.

4. How do you structure a large Express app?

Use express.Router() for modular routes. Keep business logic in separate service modules. Use middleware for cross-cutting concerns.

5. Challenge: Add input validation middleware to the Book API POST endpoint.

function validateBook(req, res, next) {
  const { title, author } = req.body;
  if (!title || !author) {
    return res.status(400).json({ error: "Title and author required" });
  }
  next();
}
app.post("/api/books", validateBook, (req, res) => { /* ... */ });

FAQ

How do I handle file uploads?
Use multer middleware. It handles multipart/form-data and saves files to disk or memory.
How do I enable CORS?
Install and use the cors package: app.use(cors()). Configure specific origins as needed.
How do I serve static files?
Use express.static("public") — files in the public folder become accessible at /filename.
What’s the difference between Express 4 and 5?
Express 5 natively handles async errors (no need for asyncHandler wrapper), supports wildcard routes, and has improved regex routing.

Try It Yourself

Run the Book API server and test endpoints:

curl http://localhost:3000/api/books
curl -X POST http://localhost:3000/api/books \
  -H "Content-Type: application/json" \
  -d '{"title":"Dune","author":"Frank Herbert","year":1965}'

What’s Next

LessonDescription
https://tutorials.dodatech.com/backend/nodejs/nodejs-database/Database integration
https://tutorials.dodatech.com/backend/nodejs/nodejs-advanced/Clustering, WebSockets
https://tutorials.dodatech.com/backend/nodejs/koa/Koa.js alternative framework
MongoDBMongoDB with Mongoose
REST APIRESTful API design

What’s Next

Congratulations on completing this Express tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro