This module covers how to build a RESTful API with Express. We'll implement CRUD operations (Create, Read, Update, Delete) and learn REST conventions.
Run: npm install then node rest-api.js
Try it live: cse135.site:3004/demo - Interactive REST API tester
REST (Representational State Transfer) is an architectural style for building web APIs. Key principles:
You'll often hear APIs described as "RESTful" rather than "REST APIs." This distinction matters. REST was defined by Roy Fielding in his 2000 doctoral dissertation with six architectural constraints:
The "Uniform Interface" constraint includes a requirement called HATEOAS (Hypermedia as the Engine of Application State): responses should include links to related actions and resources, so clients discover the API by following links rather than hardcoding URLs.
| Constraint | Strict REST | Common Practice |
|---|---|---|
| HATEOAS | Responses include links to actions | Clients hardcode API endpoints |
| Statelessness | No server-side sessions | Often use JWTs or session cookies |
| PUT vs PATCH | PUT replaces entire resource | PUT often does partial updates |
| Resource URLs | Nouns only (/users/1) |
Sometimes verbs (/users/1/activate) |
| Operation | HTTP Method | URL Pattern | Description |
|---|---|---|---|
| Create | POST | /api/items |
Create a new item |
| Read (all) | GET | /api/items |
Get all items |
| Read (one) | GET | /api/items/:id |
Get one item by ID |
| Update | PUT | /api/items/:id |
Update an item |
| Delete | DELETE | /api/items/:id |
Delete an item |
REST APIs use HTTP status codes to indicate success or failure:
| Code | Meaning | When to Use |
|---|---|---|
200 |
OK | Successful GET, PUT, DELETE |
201 |
Created | Successful POST (new resource created) |
400 |
Bad Request | Invalid input data |
404 |
Not Found | Resource doesn't exist |
500 |
Server Error | Something went wrong on the server |
Let's build a simple "items" API with in-memory storage:
const express = require('express');
const app = express();
app.use(express.json());
// In-memory "database"
let items = [
{ id: 1, name: 'Item One', completed: false },
{ id: 2, name: 'Item Two', completed: true }
];
let nextId = 3;
// GET /api/items - Get all items
app.get('/api/items', (req, res) => {
res.json(items);
});
// GET /api/items/:id - Get one item
app.get('/api/items/:id', (req, res) => {
const id = parseInt(req.params.id);
const item = items.find(i => i.id === id);
if (!item) {
return res.status(404).json({ error: 'Item not found' });
}
res.json(item);
});
// POST /api/items - Create new item
app.post('/api/items', (req, res) => {
const { name } = req.body;
// Validate input
if (!name || typeof name !== 'string') {
return res.status(400).json({ error: 'Name is required' });
}
const newItem = {
id: nextId++,
name: name.trim(),
completed: false
};
items.push(newItem);
res.status(201).json(newItem); // 201 = Created
});
// PUT /api/items/:id - Update an item
app.put('/api/items/:id', (req, res) => {
const id = parseInt(req.params.id);
const item = items.find(i => i.id === id);
if (!item) {
return res.status(404).json({ error: 'Item not found' });
}
// Update fields if provided
if (req.body.name !== undefined) {
item.name = req.body.name;
}
if (req.body.completed !== undefined) {
item.completed = Boolean(req.body.completed);
}
res.json(item);
});
// DELETE /api/items/:id - Delete an item
app.delete('/api/items/:id', (req, res) => {
const id = parseInt(req.params.id);
const index = items.findIndex(i => i.id === id);
if (index === -1) {
return res.status(404).json({ error: 'Item not found' });
}
const deleted = items.splice(index, 1)[0];
res.json({ message: 'Item deleted', item: deleted });
});
Here's how to call the API from JavaScript:
// GET all items
const items = await fetch('/api/items').then(r => r.json());
// GET one item
const item = await fetch('/api/items/1').then(r => r.json());
// POST - Create item
const newItem = await fetch('/api/items', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'New Item' })
}).then(r => r.json());
// PUT - Update item
const updated = await fetch('/api/items/1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Updated Name', completed: true })
}).then(r => r.json());
// DELETE - Remove item
const result = await fetch('/api/items/1', {
method: 'DELETE'
}).then(r => r.json());
Command-line testing:
# GET all items
curl http://localhost:3000/api/items
# GET one item
curl http://localhost:3000/api/items/1
# POST - Create item
curl -X POST http://localhost:3000/api/items \
-H "Content-Type: application/json" \
-d '{"name": "New Item"}'
# PUT - Update item
curl -X PUT http://localhost:3000/api/items/1 \
-H "Content-Type: application/json" \
-d '{"name": "Updated", "completed": true}'
# DELETE - Remove item
curl -X DELETE http://localhost:3000/api/items/1
Consistent error responses make APIs easier to use:
// Always return JSON with consistent structure
// Success: { data: ... } or the resource directly
// Error: { error: "message" }
app.get('/api/items/:id', (req, res) => {
const id = parseInt(req.params.id);
// Validate ID format
if (isNaN(id)) {
return res.status(400).json({ error: 'Invalid ID format' });
}
const item = items.find(i => i.id === id);
if (!item) {
return res.status(404).json({ error: 'Item not found' });
}
res.json(item);
});
If your API will be called from a different domain, you need CORS headers:
// Simple CORS middleware
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type');
// Handle preflight
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
// Or use the cors package:
// npm install cors
// const cors = require('cors');
// app.use(cors());
Test a real REST API running on our server:
This interactive demo lets you:
/api/items/api/items/api/items/:id/api/items/:idNote: The demo uses shared in-memory storage, so you'll see items created by other students!
View the source: rest-live-demo.js
| Concept | Key Points |
|---|---|
| REST | Resources + HTTP methods + stateless + JSON |
| URL Design | /api/resource and /api/resource/:id |
| GET | Read data, no body, idempotent |
| POST | Create data, return 201 |
| PUT | Update data, idempotent |
| DELETE | Remove data, idempotent |
| Status Codes | 200 OK, 201 Created, 400 Bad Request, 404 Not Found |