Overview

Express is the most widely used web framework for Node.js. This tutorial builds a complete REST API with CRUD endpoints, validation, error handling, and a clean project structure.

Prerequisites

Step 1: Initialize the Project

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

Add scripts to package.json:

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

Step 2: Project Structure

express-api/
├── src/
│   ├── routes/
│   │   └── users.js
│   ├── middleware/
│   │   └── errorHandler.js
│   ├── data/
│   │   └── store.js
│   └── server.js
└── package.json

Step 3: Create the Server

// src/server.js
const express = require('express');
const usersRouter = require('./routes/users');
const errorHandler = require('./middleware/errorHandler');

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

app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.use('/api/users', usersRouter);

app.use((req, res) => res.status(404).json({ error: 'Not found' }));
app.use(errorHandler);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server on port ${PORT}`));

Step 4: Build the Routes

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

let users = [
  { id: 1, name: 'Alice', email: 'alice@example.com' }
];
let nextId = 2;

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

// Read one
router.get('/:id', (req, res, next) => {
  const user = users.find(u => u.id === Number(req.params.id));
  if (!user) return next({ status: 404, message: 'User not found' });
  res.json(user);
});

// Create
router.post('/', (req, res, next) => {
  const { name, email } = req.body;
  if (!name || !email) {
    return next({ status: 400, message: 'name and email are required' });
  }
  const user = { id: nextId++, name, email };
  users.push(user);
  res.status(201).json(user);
});

// Update
router.put('/:id', (req, res, next) => {
  const user = users.find(u => u.id === Number(req.params.id));
  if (!user) return next({ status: 404, message: 'User not found' });
  const { name, email } = req.body;
  if (name !== undefined) user.name = name;
  if (email !== undefined) user.email = email;
  res.json(user);
});

// Delete
router.delete('/:id', (req, res, next) => {
  const index = users.findIndex(u => u.id === Number(req.params.id));
  if (index === -1) return next({ status: 404, message: 'User not found' });
  users.splice(index, 1);
  res.status(204).end();
});

module.exports = router;

Step 5: Add Error Handling Middleware

// src/middleware/errorHandler.js
module.exports = (err, req, res, next) => {
  const status = err.status || 500;
  const message = err.message || 'Internal server error';
  if (status >= 500) console.error(err);
  res.status(status).json({ error: message });
};

HTTP Status Code Reference

CodeMeaningUsed when
200OKSuccessful GET or PUT
201CreatedSuccessful POST
204No ContentSuccessful DELETE
400Bad RequestValidation failure
404Not FoundResource does not exist
500Internal Server ErrorUnhandled exception

Testing the API with curl

# List users
curl http://localhost:3000/api/users

# Create a user
curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Bob","email":"bob@example.com"}'

# Update
curl -X PUT http://localhost:3000/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice Smith"}'

# Delete
curl -X DELETE http://localhost:3000/api/users/1

Next Steps

  • Replace the in-memory array with a database client such as pg or mongoose.
  • Add input validation with zod or joi.
  • Add authentication middleware using JWT or sessions.
  • Add request logging with morgan or pino.
  • Write integration tests with supertest and vitest.