Skip to main content
Node.js & Express·Lesson 2 of 5

Express Setup and Basics

Express is the most popular Node.js web framework. It provides a thin layer of features on top of Node's built-in http module, making it fast and straightforward to build web applications and APIs.

Installing Express

Start a new project and install Express:

mkdir my-api && cd my-api
npm init -y
npm install express
npm install -D nodemon

Add a dev script to package.json:

{
  "scripts": {
    "start": "node src/app.js",
    "dev": "nodemon src/app.js"
  }
}

Your First Express Server

// src/app.js
const express = require("express");
const app = express();

const PORT = process.env.PORT || 3000;

app.get("/", (req, res) => {
  res.json({ message: "Welcome to the API" });
});

app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`);
});

Run it with npm run dev and visit http://localhost:3000 in your browser.

Understanding Request and Response

Every route handler receives req (request) and res (response) objects.

The Request Object

app.get("/users/:id", (req, res) => {
  console.log(req.method);        // "GET"
  console.log(req.path);          // "/users/5"
  console.log(req.params.id);     // "5"
  console.log(req.query);         // { sort: "name" } for /users/5?sort=name
  console.log(req.headers);       // request headers object
  console.log(req.body);          // request body (needs middleware)
});

The Response Object

app.get("/example", (req, res) => {
  // JSON response
  res.json({ status: "ok" });

  // Text response
  res.send("Hello, World!");

  // Status code + response
  res.status(201).json({ created: true });

  // Redirect
  res.redirect("/new-location");

  // Set headers
  res.set("X-Custom-Header", "value");
});
MethodUse For
res.json()Send JSON data
res.send()Send string, buffer, or object
res.status()Set HTTP status code
res.redirect()Redirect to another URL
res.set()Set response headers

Parsing Request Bodies

Express does not parse request bodies by default. Add the built-in middleware:

const express = require("express");
const app = express();

// Parse JSON bodies
app.use(express.json());

// Parse URL-encoded form data
app.use(express.urlencoded({ extended: true }));

app.post("/users", (req, res) => {
  const { name, email } = req.body;
  console.log(`Creating user: ${name} (${email})`);
  res.status(201).json({ name, email });
});

Project Structure

Organize your code as the project grows:

my-api/
├── src/
   ├── app.js            # Express app setup
   ├── server.js          # Server startup
   ├── routes/
      ├── users.js       # User routes
      └── posts.js       # Post routes
   ├── controllers/
      ├── userController.js
      └── postController.js
   ├── middleware/
      ├── auth.js
      └── errorHandler.js
   └── config/
       └── index.js       # Configuration
├── package.json
└── .env

Separating App and Server

// src/app.js
const express = require("express");
const app = express();

app.use(express.json());

// Mount routes
app.use("/api/users", require("./routes/users"));
app.use("/api/posts", require("./routes/posts"));

module.exports = app;
// src/server.js
const app = require("./app");
const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

This separation makes your app easier to test because you can import app without starting the server.

Route Organization

Extract routes into separate files:

// src/routes/users.js
const express = require("express");
const router = express.Router();

const users = [
  { id: 1, name: "Alice", email: "alice@example.com" },
  { id: 2, name: "Bob", email: "bob@example.com" },
];

router.get("/", (req, res) => {
  res.json(users);
});

router.get("/:id", (req, res) => {
  const user = users.find((u) => u.id === parseInt(req.params.id));
  if (!user) {
    return res.status(404).json({ error: "User not found" });
  }
  res.json(user);
});

router.post("/", (req, res) => {
  const { name, email } = req.body;
  if (!name || !email) {
    return res.status(400).json({ error: "Name and email are required" });
  }
  const newUser = { id: users.length + 1, name, email };
  users.push(newUser);
  res.status(201).json(newUser);
});

module.exports = router;

Configuration Module

Centralize your configuration:

// src/config/index.js
require("dotenv").config();

module.exports = {
  port: process.env.PORT || 3000,
  nodeEnv: process.env.NODE_ENV || "development",
  db: {
    host: process.env.DB_HOST || "localhost",
    port: process.env.DB_PORT || 5432,
    name: process.env.DB_NAME || "myapp",
  },
  jwtSecret: process.env.JWT_SECRET || "dev-secret",
};

Error Handling

Add a global error handler:

// src/middleware/errorHandler.js
function errorHandler(err, req, res, next) {
  console.error(err.stack);

  const statusCode = err.statusCode || 500;
  const message =
    process.env.NODE_ENV === "production"
      ? "Internal Server Error"
      : err.message;

  res.status(statusCode).json({
    error: message,
    ...(process.env.NODE_ENV !== "production" && { stack: err.stack }),
  });
}

module.exports = errorHandler;

Register it after all routes:

// src/app.js
const errorHandler = require("./middleware/errorHandler");

// ... routes go here

app.use(errorHandler);

Practical Exercise

Set up a complete Express project with proper structure:

// src/app.js
const express = require("express");
const config = require("./config");
const errorHandler = require("./middleware/errorHandler");

const app = express();

app.use(express.json());

// Health check
app.get("/health", (req, res) => {
  res.json({
    status: "ok",
    environment: config.nodeEnv,
    uptime: process.uptime(),
  });
});

// API routes
app.use("/api/users", require("./routes/users"));

// 404 handler
app.use((req, res) => {
  res.status(404).json({ error: `Route ${req.path} not found` });
});

// Error handler
app.use(errorHandler);

module.exports = app;

Key Takeaways

  • Express adds routing, middleware, and convenience methods on top of Node's http module.
  • Use express.json() middleware to parse JSON request bodies.
  • Separate your app setup from server startup for testability.
  • Organize routes into separate files using express.Router().
  • Always add a global error handler as the last middleware.