DEV Community

Orbit Websites
Orbit Websites

Posted on

2026 Guide: Building a REST API with Node.js and Express

2026 Guide: Building a REST API with Node.js and Express

Let’s be honest: REST APIs aren’t going anywhere. Even in 2026, they’re still the backbone of most web services — simple, predictable, and battle-tested. Node.js and Express remain a go-to combo for building them fast, especially when you need something lightweight and scalable. This guide cuts through the noise and shows you how to build a clean, production-ready REST API in 2026 — no hype, just working code.


1. Start with the Right Setup

Don’t just npm init -y && npm install express. That’s how you end up with a mess. Use a modern setup from day one.

npm init -y
npm install express dotenv cors helmet morgan
npm install --save-dev nodemon eslint prettier
Enter fullscreen mode Exit fullscreen mode
  • helmet: Security headers, non-negotiable.
  • cors: Unless you love CORS error in the console.
  • morgan: Logging HTTP requests — essential for debugging.
  • dotenv: Environment variables. Keep secrets out of code.

Set up your package.json scripts:

"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js",
  "lint": "eslint .",
  "format": "prettier --write ."
}
Enter fullscreen mode Exit fullscreen mode

And your basic server.js:

import express from "express";
import cors from "cors";
import helmet from "helmet";
import morgan from "morgan";
import dotenv from "dotenv";

dotenv.config();

const app = express();

app.use(helmet());
app.use(cors());
app.use(morgan("dev"));
app.use(express.json());

app.get("/", (req, res) => {
  res.json({ message: "REST API alive in 2026" });
});

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Run npm run dev and you’re live.


2. Structure Your Code (Don’t Dump Everything in server.js)

You’re not writing a tutorial — you’re building something maintainable. Use a basic structure:

/src
  /routes
  /controllers
  /middleware
  /utils
  server.js
Enter fullscreen mode Exit fullscreen mode

Example: /src/routes/todos.js

import { Router } from "express";
import { getTodos, createTodo } from "../controllers/todoController.js";

const router = Router();

router.route("/").get(getTodos).post(createTodo);

export default router;
Enter fullscreen mode Exit fullscreen mode

Controller: /src/controllers/todoController.js

let todos = []; // Pretend this is a DB

export const getTodos = (req, res) => {
  res.json(todos);
};

export const createTodo = (req, res) => {
  const { title } = req.body;
  if (!title) return res.status(400).json({ error: "Title required" });

  const todo = { id: todos.length + 1, title, done: false };
  todos.push(todo);
  res.status(201).json(todo);
};
Enter fullscreen mode Exit fullscreen mode

Then in server.js:

import todoRoutes from "./routes/todos.js";
app.use("/api/todos", todoRoutes);
Enter fullscreen mode Exit fullscreen mode

Clean. Scalable. No 500-line server.js.


3. Add Middleware for Validation and Errors

Don’t trust client input. Validate early.

Create /src/middleware/validateTodo.js:

export const validateTodo = (req, res, next) => {
  const { title } = req.body;
  if (!title || typeof title !== "string" || title.trim().length === 0) {
    return res.status(400).json({ error: "Valid title is required" });
  }
  next();
};
Enter fullscreen mode Exit fullscreen mode

Use it in your route:

router.route("/").get(getTodos).post(validateTodo, createTodo);
Enter fullscreen mode Exit fullscreen mode

Also, handle errors globally. Create /src/middleware/errorHandler.js:

export const errorHandler = (err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: "Something went wrong!" });
};
Enter fullscreen mode Exit fullscreen mode

And in server.js:

app.use(errorHandler);
Enter fullscreen mode Exit fullscreen mode

Now unhandled errors won’t crash your server or return HTML.


4. Use Environment Variables and Config

No hardcoded ports, URLs, or secrets.

.env:

PORT=5000
NODE_ENV=development
API_BASE=/api
Enter fullscreen mode Exit fullscreen mode

Create /src/config.js:

export default {
  port: process.env.PORT || 5000,
  apiBase: process.env.API_BASE || "/api",
  env: process.env.NODE_ENV || "development",
};
Enter fullscreen mode Exit fullscreen mode

Use it in server.js:

import config from "./config.js";
app.listen(config.port, () => {
  console.log(`Server running on port ${config.port}`);
});
Enter fullscreen mode Exit fullscreen mode

Now you can deploy to different environments without touching code.


5. Add Basic Testing (Yes, Really)

You don’t need Jest + 10 libraries. Use supertest and node:test.

Install:

npm install --save-dev supertest
Enter fullscreen mode Exit fullscreen mode

Create /tests/todo.test.js:


js
import { describe, it } from "node:test";
import assert from "node:assert";
import request from "supertest";
import app from "../src/server.js";



---

☕ **Playful**
Enter fullscreen mode Exit fullscreen mode

Top comments (0)