Nodejs Simple Restful Routing Without Frameworks Complete Guide
Understanding the Core Concepts of NodeJS Simple RESTful Routing Without Frameworks
NodeJS Simple RESTful Routing Without Frameworks
Understanding the Basics
Before we begin, it’s essential to grasp the fundamental components of a RESTful API:
HTTP Methods: These include GET, POST, PUT, DELETE, and others. Each method corresponds to a specific action:
- GET: Retrieve data.
- POST: Create new resources.
- PUT: Update existing resources.
- DELETE: Remove resources.
Routes: These define the endpoints of your API, such as
/api/users
,/api/products
, etc.Responses: These are usually in JSON format, indicating the result of the requested operation.
Requests: These include headers, query strings, and request bodies that clients send to the server.
Setting Up the Node.js Server
Let's begin by setting up a basic HTTP server in Node.js. You'll need Node.js installed on your machine to proceed.
Step 1: Initialize Your Project
First, create a directory for your project and navigate into it using the command line. Then, initialize a new Node.js project:
mkdir simple-api
cd simple-api
npm init -y
Step 2: Create the Server File
Create a new file named server.js
. This is where our server logic will reside.
touch server.js
Step 3: Write the Basic Server Code
Open server.js
and add the following code to create a basic HTTP server:
const http = require('http');
// Port number
const PORT = 3000;
// Create server
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Hello, World!' }));
});
// Start server
server.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
This snippet creates a basic HTTP server that listens on port 3000 and returns a JSON response containing a message. We use the http
module, which is part of Node.js's standard library.
Step 4: Run the Server
To run your server, execute the following command in your terminal:
node server.js
You should see the message "Server is listening on port 3000" in your terminal. Open your web browser and navigate to http://localhost:3000/
to see the JSON response.
Implementing RESTful Routing
Now that we have a basic server, we can start implementing RESTful routes. We'll focus on creating a simple API for a resource, say books
.
Step 5: Define the Routes
We will create the following routes for our books
resource:
- GET /api/books: Retrieve all books.
- GET /api/books/:id: Retrieve a specific book by ID.
- POST /api/books: Create a new book.
- PUT /api/books/:id: Update an existing book.
- DELETE /api/books/:id: Delete a specific book.
Let's add this functionality to our server.js
file:
const http = require('http');
const url = require('url');
const querystring = require('querystring');
const PORT = 3000;
let books = [
{ id: 1, title: '1984', author: 'George Orwell' },
{ id: 2, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' },
];
const server = http.createServer((req, res) => {
let { pathname, query } = url.parse(req.url, true);
let method = req.method;
res.setHeader('Content-Type', 'application/json');
// GET /api/books
if (pathname === '/api/books' && method === 'GET') {
res.end(JSON.stringify(books));
// POST /api/books
} else if (pathname === '/api/books' && method === 'POST') {
let requestBody = '';
req.on('data', chunk => {
requestBody += chunk;
});
req.on('end', () => {
let book = JSON.parse(requestBody);
book.id = books.length + 1;
books.push(book);
res.writeHead(201);
res.end(JSON.stringify(book));
});
// GET /api/books/:id
} else if (pathname.match(/^\/api\/books\/(\d+)$/) && method === 'GET') {
let id = parseInt(pathname.split('/')[3], 10);
let book = books.find(b => b.id === id);
if (book) {
res.end(JSON.stringify(book));
} else {
res.writeHead(404);
res.end(JSON.stringify({ message: 'Book not found' }));
}
// PUT /api/books/:id
} else if (pathname.match(/^\/api\/books\/(\d+)$/) && method === 'PUT') {
let id = parseInt(pathname.split('/')[3], 10);
let requestBody = '';
req.on('data', chunk => {
requestBody += chunk;
});
req.on('end', () => {
let updatedBook = JSON.parse(requestBody);
let bookIndex = books.findIndex(b => b.id === id);
if (bookIndex !== -1) {
updatedBook.id = id;
books[bookIndex] = updatedBook;
res.end(JSON.stringify(updatedBook));
} else {
res.writeHead(404);
res.end(JSON.stringify({ message: 'Book not found' }));
}
});
// DELETE /api/books/:id
} else if (pathname.match(/^\/api\/books\/(\d+)$/) && method === 'DELETE') {
let id = parseInt(pathname.split('/')[3], 10);
let bookIndex = books.findIndex(b => b.id === id);
if (bookIndex !== -1) {
books.splice(bookIndex, 1);
res.writeHead(204);
res.end();
} else {
res.writeHead(404);
res.end(JSON.stringify({ message: 'Book not found' }));
}
} else {
res.writeHead(404);
res.end(JSON.stringify({ message: 'Not Found' }));
}
});
server.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
Here's what we've done:
- GET /api/books: Returns the list of books.
- POST /api/books: Creates a new book using the request body. The book is added to the
books
array, and the created book is returned with a201 Created
status. - GET /api/books/:id: Retrieves a specific book by ID. If the book is found, it is returned; otherwise, a
404 Not Found
response is sent. - PUT /api/books/:id: Updates an existing book. The book's ID is used to locate the book in the array, and its properties are updated based on the request body. If the book exists, the updated book is returned; otherwise, a
404 Not Found
response is sent. - DELETE /api/books/:id: Deletes a book by ID. If the book is found, it is removed from the array and a
204 No Content
response is sent. Otherwise, a404 Not Found
response is sent.
Handling PUT and DELETE Requests
When dealing with PUT
and DELETE
requests, it's crucial to consider a few factors:
- Validation: Ensure that the request body contains valid data. For example, when updating a book, make sure it includes a
title
andauthor
. - ID Handling: Always validate and parse the resource ID correctly to avoid unexpected behavior or security issues.
- Error Responses: Provide informative error messages to help clients understand what went wrong during the request.
Testing the API
To test your API, you can use tools like Postman or cURL. Here are some examples using cURL:
GET Request
curl -X GET http://localhost:3000/api/books
POST Request
curl -X POST http://localhost:3000/api/books -H "Content-Type: application/json" -d '{"title": "To Kill a Mockingbird", "author": "Harper Lee"}'
GET Specific Book
curl -X GET http://localhost:3000/api/books/1
PUT Request
curl -X PUT http://localhost:3000/api/books/1 -H "Content-Type: application/json" -d '{"title": "1984 Revisited", "author": "George Orwell"}'
DELETE Request
curl -X DELETE http://localhost:3000/api/books/1
Conclusion
Creating a simple RESTful API in Node.js without using frameworks like Express.js provides a profound understanding of how HTTP servers work and the mechanics of routing and request handling. While frameworks offer convenience and speed, building from scratch ensures you have complete control over your API and a solid grasp of the underlying principles.
Remember to handle edge cases, validate data, and consider security best practices when building your API. As you progress, you can explore adding features like middleware, more robust error handling, and database integration.
By mastering these foundational concepts, you'll be better equipped to leverage modern frameworks and libraries to build more complex and scalable applications.
Keywords:
Online Code run
Step-by-Step Guide: How to Implement NodeJS Simple RESTful Routing Without Frameworks
Step-by-Step Guide to Create a Simple RESTful API in Node.js
Step 1: Initialize Your Project
First, create a new directory for your project and initialize a new Node.js project using npm.
mkdir node-rest-api
cd node-rest-api
npm init -y
Step 2: Create the Server File
Create a new file called server.js
:
touch server.js
Step 3: Write the RESTful API Code
Open server.js
in your editor and add the following code:
const http = require('http');
const url = require('url');
const port = 3000;
let users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
const server = http.createServer((req, res) => {
let parsedUrl = url.parse(req.url, true);
let pathname = parsedUrl.pathname;
res.setHeader('Content-Type', 'application/json');
// Routes for managing users
if (pathname === '/users' && req.method === 'GET') {
// Basic GET request to retrieve all users
res.statusCode = 200;
res.end(JSON.stringify(users));
} else if (pathname.startsWith('/users/') && req.method === 'GET') {
// GET request to retrieve a user by ID
let id = parseInt(pathname.split('/').pop(), 10);
let user = users.find(u => u.id === id);
if (user) {
res.statusCode = 200;
res.end(JSON.stringify(user));
} else {
res.statusCode = 404;
res.end(JSON.stringify({ message: 'User not found' }));
}
} else if (pathname === '/users' && req.method === 'POST') {
// POST request to create a new user
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
let newUser = JSON.parse(body);
newUser.id = users.length + 1;
users.push(newUser);
res.statusCode = 201;
res.end(JSON.stringify(newUser));
});
} else if (pathname.startsWith('/users/') && req.method === 'PUT') {
// PUT request to update a user by ID
let id = parseInt(pathname.split('/').pop(), 10);
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
let updatedUser = JSON.parse(body);
let userIndex = users.findIndex(u => u.id === id);
if (userIndex !== -1) {
users[userIndex] = { ...users[userIndex], ...updatedUser };
res.statusCode = 200;
res.end(JSON.stringify(users[userIndex]));
} else {
res.statusCode = 404;
res.end(JSON.stringify({ message: 'User not found' }));
}
});
} else if (pathname.startsWith('/users/') && req.method === 'DELETE') {
// DELETE request to delete a user by ID
let id = parseInt(pathname.split('/').pop(), 10);
let userIndex = users.findIndex(u => u.id === id);
if (userIndex !== -1) {
let deletedUser = users.splice(userIndex, 1);
res.statusCode = 200;
res.end(JSON.stringify({ message: 'User deleted', user: deletedUser }));
} else {
res.statusCode = 404;
res.end(JSON.stringify({ message: 'User not found' }));
}
} else {
// If no route is found
res.statusCode = 404;
res.end(JSON.stringify({ message: 'Not found' }));
}
});
server.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
Explanation
- Parsing URL: The
url
module is used to parse the URL and get the pathname. - Handling HTTP Methods: We handle
GET
,POST
,PUT
, andDELETE
methods according to the pathname. - State Management: We use an in-memory array (
users
) to store user data. This data is lost when the server restarts. - End Points:
- GET /users: Retrieve all users.
- GET /users/:id: Retrieve a user by ID.
- POST /users: Create a new user.
- PUT /users/:id: Update a user by ID.
- DELETE /users/:id: Delete a user by ID.
Step 4: Test Your API
You can test your API using tools like curl, Postman, or Insomnia. Here are some example commands using curl
:
Top 10 Interview Questions & Answers on NodeJS Simple RESTful Routing Without Frameworks
1. How can I create a basic HTTP server in Node.js to handle requests?
Answer: To create a basic HTTP server, you can use the built-in http
module in Node.js. Here’s a minimal example:
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello World\n");
});
server.listen(3000, () => console.log("Server is running on http://localhost:3000"));
This code snippet creates an HTTP server that listens on port 3000 and responds with "Hello World" to all requests.
2. What is the difference between res.write()
and res.end()
methods?
Answer:
- The
res.write()
method sends a chunk of the response body. It can be called multiple times to provide successive parts of the body. - The
res.end()
method signals that the response is complete; it flushes the remaining response data to the network if there is any and ends the request-response cycle. After callingres.end()
, you should not modify the headers or write to the response anymore.
3. How do I handle different routes in a Node.js server?
Answer: Handling different routes manually involves checking the URL and matching it against known paths. Here’s an example:
const http = require('http');
const url = require('url');
const server = http.createServer((req, res) => {
const pathName = url.parse(req.url, true).pathname;
if (pathName === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>Home Page</h1>');
} else if (pathName === '/about') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>About Us</h1>');
} else {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('<h1>Page Not Found</h1>');
}
});
server.listen(3000, () => console.log('Server is running on http://localhost:3000'));
In this snippet, url.parse(req.url, true)
parses the URL of the incoming request and returns an object containing its components, including the pathname. You can then use this pathname
to handle different routes accordingly.
4. How can I handle POST requests without a framework?
Answer: Handling POST requests requires reading the request body, which is sent as stream data. Here’s an example:
const http = require('http');
let bodyParser = require('./body-parser'); // Assume you have a body-parser
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/submit') {
bodyParser(req, (postData) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Data received', data: postData }));
});
} else {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('<h1>Route not found</h1>');
}
});
server.listen(3000);
You would need to create a body-parser
function similar to this one:
function bodyParser(req, callback) {
let body = '';
req.on('data', function (data) {
body += data;
if (body.length > 1e6) // Limit to 1MB
req.connection.destroy();
});
req.on('end', function () {
try {
body = JSON.parse(body);
} catch (e) {
return callback(`[]`);
}
callback(body);
});
}
5. How do I deal with different HTTP methods (GET, POST, PUT, DELETE)?
Answer: Check req.method
to determine the type of request and handle each accordingly:
const http = require('http');
const server = http.createServer((req, res) => {
const pathName = req.url;
if (req.method === 'GET') {
if (pathName === '/data') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'GET data' }));
}
} else if (req.method === 'POST') {
if (pathName === '/data') {
bodyParser(req, (postData) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'POST data', data: postData }));
});
}
} else if (req.method === 'PUT') {
if (pathName === '/data/:id') {
// Implement logic to handle PUT requests
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Data updated' }));
}
} else if (req.method === 'DELETE') {
if (pathName === '/data/:id') {
// Implement logic to handle DELETE requests
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Data deleted' }));
}
} else {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('<h1>Method not allowed</h1>');
}
});
server.listen(3000);
Remember to replace bodyParser
with the function created above to handle POST requests.
6. How do I parse JSON request bodies?
Answer: As shown in Answer 4, parsing JSON request bodies typically involves reading the request stream and converting it into a string that can be parsed by JSON.parse()
. Ensure correct Content-Type (application/json
) is set on the request.
7. How can I send JSON responses from my Node.js server?
Answer: Setting the 'Content-Type'
to 'application/json'
in your response headers allows clients to understand that the body of your response contains JSON data:
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({message: 'Success', data: someData}));
8. How do I handle URL parameters in Node.js RESTful routing?
Answer: URL parameters can be captured by splitting the URL and reading individual segments. For example:
const http = require('http');
const url = require('url');
const server = http.createServer((req, res) => {
const pathName = url.parse(req.url, true).pathname;
const id = parseInt(pathName.split('/')[2]);
if (req.method === 'GET' && !isNaN(id)) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: `Getting data for item ${id}`}));
} else {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('<h1>ID not found</h1>');
}
});
server.listen(3000, () => console.log('Server is running on http://localhost:3000'));
9. Can I implement middleware functionality in Node.js without frameworks?
Answer: Middleware-like functionality can be implemented manually by defining functions that act on requests and responses before reaching the handler logic. Here’s an example:
const http = require('http');
const url = require('url');
// Middleware Function to log requests
function logger(req, res, next) {
console.log(`${req.method} request for ${req.url}`);
next();
}
const server = http.createServer((req, res) => {
const pathName = url.parse(req.url, true).pathname;
logger(req, res, () => { // Invoking Logger Middleware
if (pathName === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>Home Page</h1>');
} else {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('<h1>Page Not Found</h1>');
}
});
});
server.listen(3000, () => console.log('Server running on http://localhost:3000'));
10. How can I validate request data before processing a request in Node.js?
Answer: Validation logic can be added after parsing the request body. You can either implement this manually or use libraries like Joi for more complex validations. An example of manual validation is:
function validatePostData(data) {
if (!data.name || typeof data.name !== 'string')
throw new Error('Name is required and must be a string!');
if (!data.age || typeof data.age !== 'number')
throw new Error('Age is required and must be a number!');
}
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/users') {
bodyParser(req, (postData) => {
try {
validatePostData(postData);
} catch(e) {
res.writeHead(400, {'Content-Type': 'application/json'});
return res.end(JSON.stringify({message: e.message}));
}
// Process valid data here...
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({message: 'User added successfully!', user: postData}));
});
}
});
This code performs basic validation ensuring that the POSTed data includes a name
property which is a string, and an age
property which is a number before processing it further.
Login to post a comment.