Skip to content

Node.js RESTFul API

REST stands for REpresentational State Transfer. It’s an architectural style, not a specific technology or protocol, for designing networked applications. RESTful systems (systems adhering to REST principles) typically use the HTTP protocol for communication.

Key principles of REST include:

  • Client-Server Architecture: Separation of concerns between the client (requesting data) and the server (managing data).
  • Statelessness: Each request from a client to the server must contain all the information needed to understand and process the request. The server does not store any client context between requests.
  • Cacheability: Responses must define themselves as cacheable or non-cacheable to improve performance.
  • Layered System: A client cannot ordinarily tell whether it is connected directly to the end server or to an intermediary along the way.
  • Uniform Interface: A common interface simplifies and decouples the architecture. This often involves:
  • Resource Identification: Resources (e.g., users, products) are identified using URIs (Uniform Resource Identifiers), like /users/123.
  • Resource Manipulation Through Representations: Clients interact with resources via their representations (commonly JSON or XML).
  • Self-descriptive Messages: Each message includes enough information to describe how to process it (e.g., using HTTP methods and headers like Content-Type).
  • Hypermedia as the Engine of Application State (HATEOAS): Responses can include links to related actions or resources, allowing clients to navigate the API dynamically (less commonly implemented in practice).

RESTful APIs leverage standard HTTP methods (verbs) to perform operations on resources:

HTTP MethodActionExample URIDescription
GETRead/usersRetrieve a list of users.
GETRead/users/123Retrieve a specific user with ID 123.
POSTCreate/usersCreate a new user (data sent in request body).
PUTUpdate/Replace/users/123Update/replace the user with ID 123 (entire resource data sent in request body).
PATCHPartial Update/users/123Partially update the user with ID 123 (only changed fields sent in request body).
DELETEDelete/users/123Delete the user with ID 123.

Web services designed according to REST principles are called RESTful web services or RESTful APIs. They provide a standardized way for different applications (e.g., a web frontend, a mobile app, another backend service) to communicate and exchange data, typically using HTTP and JSON.

Creating a Simple RESTful API with Express

Section titled “Creating a Simple RESTful API with Express”

We’ll use the popular Express.js framework to build a simple API for managing a list of users. For simplicity, we’ll store the user data in a JSON file (users.json) acting as a basic database.

Create a new directory for your project, navigate into it, and initialize npm:

mkdir simple-rest-api
cd simple-rest-api
npm init -y

Install Express:

npm install express

Create a file named users.json with some initial data:

{
"1": {
"name": "Alice",
"profession": "Engineer",
"id": 1
},
"2": {
"name": "Bob",
"profession": "Designer",
"id": 2
},
"3": {
"name": "Charlie",
"profession": "Manager",
"id": 3
}
}

Create a file named server.js and set up the basic Express server:

const express = require('express');
const fs = require('fs').promises; // Use promises API for async operations
const path = require('path');
const app = express();
const port = 3000;
const usersFilePath = path.join(__dirname, 'users.json');
// Middleware to parse JSON request bodies
app.use(express.json());
// --- API Endpoints ---
// Helper function to read users data
async function getUsersData() {
try {
const data = await fs.readFile(usersFilePath, 'utf8');
return JSON.parse(data);
} catch (error) {
// If file doesn't exist or other read error, return empty object
if (error.code === 'ENOENT') {
return {};
}
console.error("Error reading users file:", error);
throw error; // Re-throw other errors
}
}
// Helper function to write users data
async function writeUsersData(data) {
try {
await fs.writeFile(usersFilePath, JSON.stringify(data, null, 2), 'utf8');
} catch (error) {
console.error("Error writing users file:", error);
throw error;
}
}
// GET /api/users - List all users
app.get('/api/users', async (req, res) => {
try {
const users = await getUsersData();
// Return array of users instead of object keyed by ID
res.json(Object.values(users));
} catch (error) {
res.status(500).send('Error retrieving user data');
}
});
// GET /api/users/:id - Get a single user by ID
app.get('/api/users/:id', async (req, res) => {
const userId = req.params.id;
try {
const users = await getUsersData();
const user = users[userId];
if (user) {
res.json(user);
} else {
res.status(404).send('User not found');
}
} catch (error) {
res.status(500).send('Error retrieving user data');
}
});
// POST /api/users - Add a new user
app.post('/api/users', async (req, res) => {
const newUserInfo = req.body;
if (!newUserInfo || !newUserInfo.name || !newUserInfo.profession) {
return res.status(400).send('Missing user name or profession in request body');
}
try {
const users = await getUsersData();
const existingIds = Object.keys(users).map(id => parseInt(id, 10));
const newId = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 1;
const newUser = {
...newUserInfo, // Spread operator to copy properties
id: newId
};
users[newId] = newUser;
await writeUsersData(users);
res.status(201).json(newUser); // 201 Created status
} catch (error) {
res.status(500).send('Error adding user');
}
});
// DELETE /api/users/:id - Delete a user
app.delete('/api/users/:id', async (req, res) => {
const userId = req.params.id;
try {
const users = await getUsersData();
if (users[userId]) {
delete users[userId];
await writeUsersData(users);
res.status(204).send(); // 204 No Content status (success, no body)
} else {
res.status(404).send('User not found');
}
} catch (error) {
res.status(500).send('Error deleting user');
}
});
// PUT /api/users/:id - Update/Replace a user (Example)
app.put('/api/users/:id', async (req, res) => {
const userId = req.params.id;
const updatedUserInfo = req.body;
if (!updatedUserInfo || !updatedUserInfo.name || !updatedUserInfo.profession) {
return res.status(400).send('Missing user name or profession in request body for update');
}
try {
let users = await getUsersData();
if (!users[userId]) {
return res.status(404).send('User not found');
}
// Completely replace the user info
users[userId] = {
...updatedUserInfo,
id: parseInt(userId, 10) // Ensure ID remains correct type
};
await writeUsersData(users);
res.json(users[userId]);
} catch (error) {
res.status(500).send('Error updating user');
}
});
// Start the server
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});

Open your terminal in the project directory and run:

$ node server.js

You should see: Server listening at http://localhost:3000

You can use tools like curl in your terminal, Postman, Insomnia, or even your web browser (for GET requests) to interact with your API:

  • List Users (GET): Open http://localhost:3000/api/users in your browser or use curl http://localhost:3000/api/users
  • Get User 2 (GET): Open http://localhost:3000/api/users/2 or use curl http://localhost:3000/api/users/2
  • Add User (POST): Use curl -X POST -H "Content-Type: application/json" -d '{"name":"David", "profession":"Tester"}' http://localhost:3000/api/users
  • Delete User 1 (DELETE): Use curl -X DELETE http://localhost:3000/api/users/1
  • Update User 3 (PUT): Use curl -X PUT -H "Content-Type: application/json" -d '{"name":"Charlie Jr.", "profession":"Senior Manager"}' http://localhost:3000/api/users/3

After making changes with POST, PUT, or DELETE, you can use GET requests again to see the updated list in users.json.

This example demonstrates the basics of creating a RESTful API in Node.js using Express. Real-world applications would typically involve more robust error handling, input validation, authentication, authorization, and often a proper database instead of a JSON file.