A REST API follows conventions for resource-based URLs and HTTP methods. This example demonstrates a complete API with proper status codes, validation, and error handling.
Complete API Server
import { createServer } from 'node:http';
// In-memory data store
let users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
];
let nextId = 3;
// Utility functions
function parseJSON(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
try {
resolve(body ? JSON.parse(body) : {});
} catch {
reject({ status: 400, message: 'Invalid JSON' });
}
});
});
}
function sendJSON(res, status, data) {
res.writeHead(status, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
});
res.end(JSON.stringify(data));
}
function sendError(res, status, message) {
sendJSON(res, status, { error: message });
}
// Validation
function validateUser(data) {
const errors = [];
if (!data.name || data.name.length < 2) {
errors.push('Name must be at least 2 characters');
}
if (!data.email || !data.email.includes('@')) {
errors.push('Valid email is required');
}
return errors;
}Request Handler
const server = createServer(async (req, res) => {
const { method, url } = req;
const [path, id] = url.match(/^\/users(?:\/(\d+))?$/) || [];
// Handle CORS preflight
if (method === 'OPTIONS') {
res.writeHead(204, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers': 'Content-Type'
});
res.end();
return;
}
// Route: GET /users - List all users
if (method === 'GET' && path && !id) {
sendJSON(res, 200, users);
return;
}
// Route: GET /users/:id - Get single user
if (method === 'GET' && id) {
const user = users.find(u => u.id === parseInt(id));
if (!user) {
sendError(res, 404, 'User not found');
return;
}
sendJSON(res, 200, user);
return;
}
// Route: POST /users - Create user
if (method === 'POST' && path && !id) {
try {
const body = await parseJSON(req);
const errors = validateUser(body);
if (errors.length > 0) {
sendJSON(res, 400, { errors });
return;
}
const user = {
id: nextId++,
name: body.name,
email: body.email
};
users.push(user);
sendJSON(res, 201, user); // 201 Created
} catch (error) {
sendError(res, error.status || 500, error.message);
}
return;
}
// Route: PUT /users/:id - Update user
if (method === 'PUT' && id) {
try {
const index = users.findIndex(u => u.id === parseInt(id));
if (index === -1) {
sendError(res, 404, 'User not found');
return;
}
const body = await parseJSON(req);
const errors = validateUser(body);
if (errors.length > 0) {
sendJSON(res, 400, { errors });
return;
}
users[index] = {
...users[index],
name: body.name,
email: body.email
};
sendJSON(res, 200, users[index]);
} catch (error) {
sendError(res, error.status || 500, error.message);
}
return;
}
// Route: DELETE /users/:id - Delete user
if (method === 'DELETE' && id) {
const index = users.findIndex(u => u.id === parseInt(id));
if (index === -1) {
sendError(res, 404, 'User not found');
return;
}
users.splice(index, 1);
res.writeHead(204); // 204 No Content
res.end();
return;
}
// No route matched
sendError(res, 404, 'Not Found');
});
server.listen(3000);Testing the API
# List all users
curl http://localhost:3000/users
# Get single user
curl http://localhost:3000/users/1
# Create user
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name": "Charlie", "email": "charlie@example.com"}'
# Update user
curl -X PUT http://localhost:3000/users/1 \
-H "Content-Type: application/json" \
-d '{"name": "Alice Updated", "email": "alice.new@example.com"}'
# Delete user
curl -X DELETE http://localhost:3000/users/1
# Test validation error
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name": "A"}' # Too short, no emailStatus codes used:
200 OK- Successful GET/PUT201 Created- Successful POST (resource created)204 No Content- Successful DELETE (no body needed)400 Bad Request- Invalid input / validation error404 Not Found- Resource doesn't exist