Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →

Building a REST API with Node.js and Express for Beginners

Build your first REST API with Node.js and Express: routes, middleware, error handling, authentication, and connecting a database — for beginners.

A
AiTechWorlds Team
May 27, 2026 7 min read
📱

Get more content like this on Telegram!

Daily AI tips, notes & resources — free

Join Free →

Building a REST API with Node.js and Express for Beginners

My first API was a mess. I had all my code in one file, zero error handling, and hardcoded data. It worked for demos but would have fallen apart the moment two users tried to use it simultaneously.

Building APIs properly isn't complicated — it's a set of patterns. Once you know the patterns, every API you build follows the same structure and you never start from scratch again.

This tutorial builds a complete Task management API from scratch: creating tasks, reading them, updating, deleting, basic auth, and a real database. By the end, you'll have a production-ready structure you can use as a starter for any project.


Setup

mkdir task-api
cd task-api
npm init -y
npm install express cors dotenv
npm install -D nodemon

Create src/index.js:

const express = require('express');
const cors = require('cors');

const app = express();

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

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

Add to package.json:

{
  "scripts": {
    "dev": "nodemon src/index.js",
    "start": "node src/index.js"
  }
}
npm run dev

Project Structure

src/
  index.js          → Entry point, server setup
  routes/
    tasks.js        → Task routes
    auth.js         → Auth routes
  controllers/
    tasks.js        → Task business logic
    auth.js         → Auth business logic
  middleware/
    auth.js         → JWT verification
    errorHandler.js → Centralized error handling
  models/
    task.js         → Task data model

Building the Tasks API

Create the Route File

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

// In-memory storage (we'll add a real DB later)
let tasks = [
  { id: 1, title: 'Learn Express', done: false, createdAt: new Date() },
  { id: 2, title: 'Build an API', done: false, createdAt: new Date() },
];
let nextId = 3;

// GET /api/tasks — get all tasks
router.get('/', (req, res) => {
  res.json({ tasks, total: tasks.length });
});

// GET /api/tasks/:id — get single task
router.get('/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const task = tasks.find(t => t.id === id);
  
  if (!task) {
    return res.status(404).json({ error: 'Task not found' });
  }
  
  res.json(task);
});

// POST /api/tasks — create task
router.post('/', (req, res) => {
  const { title } = req.body;
  
  if (!title || typeof title !== 'string' || !title.trim()) {
    return res.status(400).json({ error: 'Title is required' });
  }
  
  const newTask = {
    id: nextId++,
    title: title.trim(),
    done: false,
    createdAt: new Date(),
  };
  
  tasks.push(newTask);
  res.status(201).json(newTask);
});

// PATCH /api/tasks/:id — update task
router.patch('/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const taskIndex = tasks.findIndex(t => t.id === id);
  
  if (taskIndex === -1) {
    return res.status(404).json({ error: 'Task not found' });
  }
  
  const { title, done } = req.body;
  const task = tasks[taskIndex];
  
  if (title !== undefined) task.title = title.trim();
  if (done !== undefined) task.done = Boolean(done);
  task.updatedAt = new Date();
  
  res.json(task);
});

// DELETE /api/tasks/:id — delete task
router.delete('/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const initialLength = tasks.length;
  tasks = tasks.filter(t => t.id !== id);
  
  if (tasks.length === initialLength) {
    return res.status(404).json({ error: 'Task not found' });
  }
  
  res.status(204).send();  // 204 No Content — success, nothing to return
});

module.exports = router;

Register the routes in index.js:

const tasksRouter = require('./routes/tasks');
app.use('/api/tasks', tasksRouter);

Test with curl:

# Get all tasks
curl http://localhost:3000/api/tasks

# Create a task
curl -X POST http://localhost:3000/api/tasks \
  -H "Content-Type: application/json" \
  -d '{"title": "New Task"}'

# Update a task
curl -X PATCH http://localhost:3000/api/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{"done": true}'

# Delete a task
curl -X DELETE http://localhost:3000/api/tasks/1

Middleware: The Express Superpower

Middleware functions run between the request and response. They're how you add authentication, logging, validation, and more.

Logging Middleware

function requestLogger(req, res, next) {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.url} ${res.statusCode} ${duration}ms`);
  });
  
  next();  // Must call next() to continue to the next middleware/route
}

app.use(requestLogger);

Centralized Error Handling

// src/middleware/errorHandler.js
function errorHandler(err, req, res, next) {
  console.error(err.stack);
  
  const status = err.status || 500;
  const message = err.message || 'Internal server error';
  
  res.status(status).json({
    error: message,
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
  });
}

module.exports = errorHandler;
// In index.js — AFTER all routes
const errorHandler = require('./middleware/errorHandler');
app.use(errorHandler);

Now in routes, you can throw errors or call next(error):

router.get('/:id', (req, res, next) => {
  try {
    const task = findTask(req.params.id);
    if (!task) {
      const error = new Error('Task not found');
      error.status = 404;
      throw error;
    }
    res.json(task);
  } catch (err) {
    next(err);  // Forwards to errorHandler
  }
});

Adding JWT Authentication

npm install jsonwebtoken bcrypt
// src/routes/auth.js
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const router = express.Router();

const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change-in-production';

// In-memory users (replace with database)
const users = [];

router.post('/register', async (req, res, next) => {
  try {
    const { email, password } = req.body;
    
    if (!email || !password) {
      return res.status(400).json({ error: 'Email and password required' });
    }
    
    const existing = users.find(u => u.email === email);
    if (existing) {
      return res.status(409).json({ error: 'Email already registered' });
    }
    
    const passwordHash = await bcrypt.hash(password, 10);
    const user = { id: Date.now(), email, passwordHash };
    users.push(user);
    
    const token = jwt.sign({ userId: user.id, email }, JWT_SECRET, { expiresIn: '7d' });
    res.status(201).json({ token });
  } catch (err) {
    next(err);
  }
});

router.post('/login', async (req, res, next) => {
  try {
    const { email, password } = req.body;
    const user = users.find(u => u.email === email);
    
    if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
    
    const token = jwt.sign({ userId: user.id, email }, JWT_SECRET, { expiresIn: '7d' });
    res.json({ token });
  } catch (err) {
    next(err);
  }
});

module.exports = router;

Auth Middleware

// src/middleware/auth.js
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change-in-production';

function requireAuth(req, res, next) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Authorization header required' });
  }
  
  const token = authHeader.split(' ')[1];
  
  try {
    const payload = jwt.verify(token, JWT_SECRET);
    req.user = payload;  // Attach user to request
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
}

module.exports = requireAuth;

Apply to protected routes:

const requireAuth = require('./middleware/auth');

// Only authenticated users can create/update/delete
router.post('/', requireAuth, (req, res) => { ... });
router.patch('/:id', requireAuth, (req, res) => { ... });
router.delete('/:id', requireAuth, (req, res) => { ... });

What to Learn Next

This API uses in-memory storage. The natural next step is connecting to a real database. For JavaScript APIs, popular choices are:

  • PostgreSQL with the pg package or Prisma ORM
  • MongoDB with Mongoose
  • SQLite for simple local databases

For calling this API from a React frontend, our React tutorial for beginners shows how to fetch and display data. When your frontend grows to need more structure, our Next.js 14 App Router guide shows how to build full-stack apps. And for understanding GraphQL as an alternative to REST, see our GraphQL vs REST guide.


Frequently Asked Questions

What is Express.js and why use it?

Express is a minimal Node.js web framework that adds routing, middleware, and HTTP helpers. It makes building APIs dramatically faster than raw Node.js.

What is a REST API?

An API that uses HTTP methods (GET/POST/PUT/DELETE) on resource URLs. Stateless — each request is independent. Returns JSON.

How do I handle errors in Express?

Create an error-handling middleware (4 params: err, req, res, next). Place it after all routes. Call next(error) in route handlers to forward to it.

How do I add authentication?

JWT: sign a token on login, verify it in middleware, attach user to req.user. Apply auth middleware to protected routes.

Share this article:

Frequently Asked Questions

Express.js is a minimal web framework for Node.js. Node.js itself can serve HTTP requests, but Express adds routing, middleware, request/response helpers, and a clean API that makes building web servers dramatically faster. It's the most popular Node.js framework with a massive ecosystem of plugins and middleware.
A

AiTechWorlds Team

✓ Verified Writer

The AiTechWorlds team is passionate about AI, technology, and education. We create high-quality, research-backed content to help you learn, grow, and succeed in the modern digital world.

Related Articles

10K+ Members Growing Daily

Get Free AI Notes Daily

Join AiTechWorlds on Telegram and get daily AI tips, prompt engineering templates, coding resources, and exclusive content — 100% free!

📚 Free Study Notes🤖 AI Tips Daily⚡ Prompt Templates💻 Coding Resources
Join Free Channel

No spam. Leave anytime.

!