Building APIs doesn’t have to be complicated. In this tutorial, we’ll build a fully functional REST API from scratch using Node.js and Express. By the end, you’ll have a working API with CRUD operations, proper error handling, and middleware.
Prerequisites
Make sure you have Node.js installed (version 18 or higher). You can check by running:
node --version
npm --version
Step 1: Project Setup
Create a new directory and initialize the project:
mkdir my-api && cd my-api
npm init -y
npm install express cors dotenv
npm install -D nodemon
Step 2: Create the Server
Create server.js in your project root:
const express = require('express');
const cors = require('cors');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
// In-memory data store
let books = [
{ id: 1, title: 'Clean Code', author: 'Robert Martin', year: 2008 },
{ id: 2, title: 'The Pragmatic Programmer', author: 'Hunt & Thomas', year: 1999 }
];
// Routes
app.get('/api/books', (req, res) => {
res.json(books);
});
app.get('/api/books/:id', (req, res) => {
const book = books.find(b => b.id === parseInt(req.params.id));
if (!book) return res.status(404).json({ error: 'Book 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 are required' });
}
const newBook = { id: books.length + 1, title, author, year };
books.push(newBook);
res.status(201).json(newBook);
});
app.put('/api/books/:id', (req, res) => {
const book = books.find(b => b.id === parseInt(req.params.id));
if (!book) return res.status(404).json({ error: 'Book not found' });
Object.assign(book, req.body);
res.json(book);
});
app.delete('/api/books/:id', (req, res) => {
const index = books.findIndex(b => b.id === parseInt(req.params.id));
if (index === -1) return res.status(404).json({ error: 'Book not found' });
books.splice(index, 1);
res.status(204).send();
});
app.listen(PORT, () => console.log('Server running on port ' + PORT));
Step 3: Add Error Handling Middleware
Add this before app.listen():
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
// Global error handler
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
Step 4: Test Your API
Start the server with node server.js and test using curl:
# Get all books
curl http://localhost:3000/api/books
# Create a new book
curl -X POST http://localhost:3000/api/books \
-H "Content-Type: application/json" \
-d '{"title":"Refactoring","author":"Martin Fowler","year":1999}'
# Update a book
curl -X PUT http://localhost:3000/api/books/1 \
-H "Content-Type: application/json" \
-d '{"year":2009}'
What’s Next?
This is a solid foundation, but for production you’d want to add a database (MongoDB or PostgreSQL), input validation (Joi or Zod), authentication (JWT), rate limiting, and proper logging. We’ll cover those in future tutorials.
