NodeJS Simple RESTful Routing Without Frameworks Step by step Implementation and Top 10 Questions and Answers
 Last Update:6/1/2025 12:00:00 AM     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    14 mins read      Difficulty-Level: beginner

Node.js Simple RESTful Routing Without Frameworks

Node.js is a powerful and scalable JavaScript runtime built on Chrome's V8 JavaScript engine, making it an ideal choice for building high-performance, server-side applications. While popular frameworks like Express.js simplify the process of creating server-side applications, it's valuable to understand how to build a RESTful API in Node.js without them. This can enhance our understanding of the underlying mechanics and provide more control over our applications.

Understanding RESTful APIs

REST (Representational State Transfer) is a software architectural style for designing networked applications. It relies on a stateless, client-server, cacheable communications protocol and is often implemented over HTTP. The five key principles of REST are:

  1. Client-Server Architecture: Separation of concerns between the client and server.
  2. Statelessness: Each client request from the client to server must contain all the information needed to understand the request, so the server can't store session information.
  3. Cacheable: Responses must be either cacheable or marked as non-cacheable.
  4. Layered System: An application can be composed of multiple layers to abstract the communication mechanisms.
  5. Uniform Interface: This is divided into four rules:
    • Identification of resources
    • Manipulation of resources through representations
    • Self-descriptive messages
    • Hypermedia as the engine of application state (HATEOAS)

Basic Components of a RESTful API

  • Resources: Represent entities in the system (e.g., users, posts).
  • Endpoints/URIs: Addresses of resources (e.g., /users, /posts).
  • HTTP Methods: Indicate actions to be performed on resources:
    • GET: Retrieve a resource.
    • POST: Create a new resource.
    • PUT/PATCH: Update a resource.
    • DELETE: Remove a resource.

Setting Up a Simple RESTful Server in Node.js

Let's walk through the process of creating a simple RESTful API for managing a list of users using Node.js without any frameworks.

  1. Initialize the Project

    mkdir node-rest-api
    cd node-rest-api
    npm init -y
    
  2. Create the Server File

    touch server.js
    
  3. Write the Server Code

    Here is a complete example of a simple RESTful API for user management using Node.js's built-in http module.

    const http = require('http');
    const url = require('url');
    
    const users = [
        { id: 1, name: 'John Doe', email: 'john@example.com' },
        { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
    ];
    
    const server = http.createServer((req, res) => {
        const uri = url.parse(req.url, true);
        const method = req.method;
        const path = uri.pathname;
    
        // Handling GET requests
        if (method === 'GET' && path === '/users') {
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify(users));
        } else if (method === 'GET' && path.match(/\/users\/\d+$/)) {
            const id = path.split('/')[2];
            const user = users.find(u => u.id === parseInt(id));
            if (user) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify(user));
            } else {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ message: 'User not found' }));
            }
        }
    
        // Handling POST requests
        else if (method === 'POST' && path === '/users') {
            let body = '';
            req.on('data', chunk => {
                body += chunk.toString();
            });
            req.on('end', () => {
                const newUser = JSON.parse(body);
                newUser.id = users.length ? users[users.length - 1].id + 1 : 1;
                users.push(newUser);
                res.writeHead(201, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify(newUser));
            });
        }
    
        // Handling PUT/PATCH requests
        else if (method.match(/PATCH|PUT/i) && path.match(/\/users\/\d+$/)) {
            const id = path.split('/')[2];
            let body = '';
            req.on('data', chunk => {
                body += chunk.toString();
            });
            req.on('end', () => {
                const updatedData = JSON.parse(body);
                const userIndex = users.findIndex(u => u.id === parseInt(id));
                if (userIndex !== -1) {
                    users[userIndex] = { ...users[userIndex], ...updatedData };
                    res.writeHead(200, { 'Content-Type': 'application/json' });
                    res.end(JSON.stringify(users[userIndex]));
                } else {
                    res.writeHead(404, { 'Content-Type': 'application/json' });
                    res.end(JSON.stringify({ message: 'User not found' }));
                }
            });
        }
    
        // Handling DELETE requests
        else if (method === 'DELETE' && path.match(/\/users\/\d+$/)) {
            const id = path.split('/')[2];
            const userIndex = users.findIndex(u => u.id === parseInt(id));
            if (userIndex !== -1) {
                users.splice(userIndex, 1);
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ message: 'User deleted successfully' }));
            } else {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ message: 'User not found' }));
            }
        }
    
        // Handling other routes
        else {
            res.writeHead(404, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ message: 'Route not found' }));
        }
    });
    
    const PORT = process.env.PORT || 3000;
    
    server.listen(PORT, () => {
        console.log(`Server running on port ${PORT}`);
    });
    

Key Points

  • GET /users: Retrieves a list of all users.
  • GET /users/:id: Retrieves a specific user by ID.
  • POST /users: Creates a new user. The user data is passed in the request body in JSON format.
  • PUT/PATCH /users/:id: Updates an existing user by ID. The updated data is passed in the request body in JSON format.
  • DELETE /users/:id: Deletes a user by ID.

Testing the API

You can test your API using tools like Postman, curl, or any HTTP client. Here are some examples using curl:

  • Get all users:

    curl http://localhost:3000/users
    
  • Get a user by ID:

    curl http://localhost:3000/users/1
    
  • Create a new user:

    curl -X POST http://localhost:3000/users -H "Content-Type: application/json" -d '{"name": "Alice Johnson", "email": "alice@example.com"}'
    
  • Update a user:

    curl -X PUT http://localhost:3000/users/1 -H "Content-Type: application/json" -d '{"name": "Johnathan Doe"}'
    
  • Delete a user:

    curl -X DELETE http://localhost:3000/users/1
    

Conclusion

Building a RESTful API without frameworks is a low-level approach that provides a deeper understanding of how HTTP requests and responses work. While frameworks like Express.js offer many advantages, such as easier middleware management and built-in routing, understanding the bare-metal operations can be beneficial for learning and optimization purposes. This example demonstrates a simple RESTful API using Node.js's built-in http module, covering key aspects of handling HTTP requests and responses.




Examples, Set Route and Run the Application: Data Flow Step-by-Step for Beginners

Introduction to Node.js Simple RESTful Routing Without Frameworks

Creating a RESTful service using Node.js can be achieved with or without frameworks such as Express.js. Many developers prefer frameworks because they offer simplicity and speed in setting up routing, middleware, and error handling. However, building a RESTful application from scratch without any framework provides an excellent opportunity to understand the underlying mechanics of how web servers work. This guide aims to walk you through the process of creating a simple RESTful API using Node.js core modules, focusing on setting routes, handling requests, and managing data flow.

Setting Up a New Node.js Project

First, let's create a simple project structure:

  1. Create a Project Directory

    mkdir node-rest-api
    cd node-rest-api
    
  2. Initialize a New Node.js Project

    npm init -y
    

    This command generates a package.json file with default values which will help us manage our dependencies.

  3. Create the Main Application File

    touch index.js
    

    Our primary server-side code will reside in this file.

Writing the Server Code

Let's write basic server code to get started.

Create a Basic HTTP Server

We will use the built-in http module to create an HTTP server.

index.js

const http = require('http');

// Define the host and port
const hostname = '127.0.0.1';
const port = 3000;

// Create the server
const server = http.createServer((req, res) => {
  // Set response header
  res.statusCode = 200;
  res.setHeader('Content-Type', 'application/json');
  
  // Send a JSON response
  res.end(JSON.stringify({ message: 'Welcome to the Node.js RESTful API!' }));
});

// Start the server
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

To start the server, run:

node index.js

And visit http://localhost:3000/ in your browser; you should see:

{"message":"Welcome to the Node.js RESTful API!"}

Creating Routes Manually

For a simple RESTful API, manually creating routes involves checking the request URL and method in the server creation callback function. Here’s an example of how to achieve some basic routing.

index.js

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  // Split the URL to get the path segments
  const path = req.url.split('/').filter(Boolean);

  // Set response header
  res.statusCode = 200;
  res.setHeader('Content-Type', 'application/json');

  // Handle different routes
  switch (req.method + ' ' + path.join('/')) {
    case 'GET users':
      res.end(JSON.stringify({ users: ['Alice', 'Bob', 'Charlie'] }));
      break;
    case 'POST user':
      if (req.headers['content-type'] === 'application/json') {
        let body = '';
        req.on('data', chunk => { body += chunk.toString(); });
        req.on('end', () => {
          try {
            const userData = JSON.parse(body);
            console.log('Received new user:', userData);
            
            // Normally, here you would save to a database and return the saved object
            res.end(JSON.stringify(userData)); 
          } catch (err) {
            res.statusCode = 400;
            res.end(JSON.stringify({ error: 'Invalid JSON' }));
          }
        });
      } else {
        res.statusCode = 400;
        res.end(JSON.stringify({ error: 'Unsupported Content-Type' }));
      }
      break;
    default:
      res.statusCode = 404;
      res.end(JSON.stringify({ error: 'Not Found' }));
  }
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

Running the Application

  1. Start the Server

    node index.js
    
  2. Test the GET Route Use curl or a browser to make a GET request to /users.

    curl http://localhost:3000/users
    

    Expected output:

    {"users":["Alice","Bob","Charlie"]}
    
  3. Test the POST Route Use curl to make a POST request to /user.

    curl -X POST -H "Content-Type: application/json" -d '{"name":"David"}' http://localhost:3000/user
    

    Expected output:

    {"name":"David"}
    

Data Flow Step by Step

Let's break down the process from when a request comes into the server to when a response is sent out.

Step 1: The Request Enters the Server

  1. HTTP Request Reception: When a client sends an HTTP request (GET, POST, etc.) to http://localhost:3000, it reaches our Node.js server.

  2. Callback Execution: The callback function inside http.createServer() gets executed for every request made to the port.

Step 2: Parsing the Request URL

  1. URL Splitting: We split the incoming request URL by slashes (/) to get individual path segments. For example, for the request URL http://localhost:3000/users/1, the segments would be ['users', '1'].

  2. Path Filtering: Using the .filter(Boolean) removes any empty strings from the array, thus providing a cleaner array of paths.

Step 3: Handling Different Routes

  1. Switch Case for Routes: Based on the request method and the joined path segments, we use a switch-case statement to determine which handler should process the request.

    • GET /users: This handles listing all users.
    • POST /user: This handles adding a new user to the system.
    • Default (404): If no matching route is found, it returns a "Not Found" error (404).

Step 4: Processing POST Requests (Specifically)

  1. Content-Type Check: Before processing the request body, we check whether the request header specifies Content-Type as application/json. If not, we return a "Bad Request" error (400).

  2. Body Accumulation: We accumulate the data chunks provided by the request stream using the req.on('data', chunk) event listener and concatenate them into a single string.

  3. Body Parsing: Once all data chunks are received (triggered by the req.on('end') event), we parse the accumulated JSON string into a JavaScript object using JSON.parse(body).

  4. Error Handling: If parsing fails, we catch the error and send a "Bad Request" response indicating invalid JSON format.

  5. Response Sending: Once the body parsing is successful, we log the received data, and then for demonstration purposes, simply echo back the same JSON object in the response.

Step 5: Sending Response to the Client

After determining the appropriate response based on the request, we set the status code and headers using res.statusCode and res.setHeader(). Finally, we serialize the JSON object using JSON.stringify(), set the correct content type (application/json), and send the data back to the client with res.end().

Conclusion

Building a RESTful API using Node.js without a framework helps in understanding HTTP server functionalities, request handling, routing, and data sending processes. While such a setup might become cumbersome and complex as applications grow, it is invaluable for learning the fundamentals and serves as a good base to transition into more advanced frameworks like Express.js for larger projects.

In our simplified example, we've created two routes—GET /users and POST /user, demonstrating the fundamental principles of RESTful routing in Node.js. As you gain more experience, you'll likely explore parsing query strings, working with different HTTP methods, handling errors more gracefully, and integrating databases for persistent data storage.

Remember, this approach is suitable for educational purposes and small toy applications. For real-world scenarios, leverage existing frameworks to handle complexities efficiently.