Introduction
Hello everyone!👋 I’m happy to be back with the second part of my Node.js exploration. After setting up My first Node.js – Mastering the Fundamentals in the previous post, today we’re taking a major step forward by taking the most important steps to create our first server!🥰
By the end of this post, you’ll have the basic knowledge needed to create a simple Node.js server, handle HTTP requests, serve static files, and build a basic API. This foundation will prepare you to build more complex backend applications with confidence.
The concepts we’ll go through:
- Understanding the Event Loop
- HTTP Module & Create Server: The HTTP Core
- Req vs Res Objects
- Simple Routing
- Loading Files: Serving Static Content
- Building a Simple API: API Structure
- Middleware: The Middleware Pattern
- Cleanup (Middleware & Handlers): Code Organisation
- Get Req Body For POST: Handling Data
1. Understanding the Event Loop
Definition
The Event Loop it's the mechanism that make Node.js non-blocking, asynchronous and yet run on a single thread. We can simply say that it’s the traffic manager that decides when and how operations are executed especially those that take time (like reading files, making HTTP requests...).
The logic is the following:
→ Node.js receives a request.
→ If it’s a quick operation (like a math calculation), it’s executed immediately in the main thread.
→ If it's a slow operation (like making HTTP requests) Node.js delegates it to a background thread (libuv thread pool) or the operating system.
→ While the task run in the background, the Event Loop keep processing other requests.
→ When the background task finishes, the result is placed into a queue, and the Event Loop executes it once the main thread is free.
Imagine this as a simple analogy:
(I hope this analogy makes more sense❗)
...You’re a waiter in a restaurant,🙃
→ You (the Event Loop) take orders from customers (requests),
→ The chefs in the kitchen (thread pool) cook the meals (long operations).
→ You don’t stand around waiting for one meal, you serve other tables meanwhile.
→ When the chefs finish, they notify you, and you deliver the food. This way, no customer gets stuck waiting, and everything runs efficiently.
Simple code example:
console.log("Start");
setTimeout(() => {
console.log("Timeout done");
}, 2000);
console.log("End");
//Output:
Start
End
Time done
2. HTTP Module & Create Server
The http module is a native, built-in Node.js library that provides the core functionalities for creating HTTP servers. The essential method is http.createServer(), which set up our server. This method accepts a callback function that executes every time a request received at the server, providing access to the req (request) and res (response) objects.
Simple Analogy
We can say that our server works as the receptionist at a hotel.🙃
→ import http from 'http': This is like hiring the receptionist and equipping them with the Instruction Manual (the HTTP Module) on how to communicate with clients.
→ http.createServer((req, res) => { ... }): This is setting the receptionist at the desk and establishing the fundamental rule: "Every time a client (req) makes a request, you must give them a response (res)."
→ server.listen(3000): This is like installing a telephone(on a specific port, like extension 3000) that clients can use to call and access your services.
Simple code example
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World!');
});
server.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});
http.createServer() → creates the server,
req → represents the incoming request,
res → represents the outgoing response.
3. Req vs Res Objects
The req (Request) Object Definition:
The req (Request) object is an instance of the IncomingMessage stream created by Node.js every time the server receives an HTTP request. It holds all the information sent by the client, including the HTTP method (GET, POST, etc.), the requested URL path, and the full set of headers.
The res (Response) Object Definition:
The res (Response) object is a Writable Stream through which your server sends data back to the client. Its role is to control exactly what the client receives and how they receive it, allowing you to set the HTTP Status Code, define Headers (like Content-Type), and send the response body.
Simple Analogy
req → represents the request coming from the client.
It’s like a guest walking up to the receptionist and saying what they want:
"I’d like a single room for two nights."
"Can you tell me the check-out time?" 🙃
→ The receptionist (server) receives this request and reads it to understand what action to take.
→ So req = the letter or message from the client to the receptionist.
res→ represents the response the server sends back to the client.
It’s like the receptionist replying to the guest:
"Sure, your room is booked for two nights."
"Check-out is at 12:00 PM."
→ The receptionist uses the information from the request (req) to provide the correct response (res).
→ So res = the response or message from the receptionist to the client.
Simple code:
import http from 'http';
const server = http.createServer((req, res) => {
const url = req.url;
console.log(`Path requested: ${url}`);
// we can set the Status Code and Header (using the res object)
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
// here we build and send the response body (using the res object)
res.end(
`<h1>Hello from Node.js!</h1>
<p>You asked for the path: ${url}</p>`
);
});
const PORT = 8000;
server.listen(PORT, () => console.log(`Server running at port ${PORT}`));
// server starts on the specified port
4. Simple Routing
Definition
Routing is simply how your server decides where to send an incoming request. Think of it as looking at the address (the URL path) and the goal (the HTTP Method, like GET or POST) to figure out which piece of code (handler) should run to create the correct response.
Simple Code
import http from 'http';
const server = http.createServer((req, res) => { //create server
const url = req.url;
const method = req.method; //could be GET/POST/PUT/DELETE...
//here we set default headers for a standard response
res.setHeader('Content-Type', 'text/html'); //type html
// 1. basic routing based on URL
if (url === '/') {
res.statusCode = 200;
res.end('<h1>Home Page!</h1>');
}
//2. we check if the route is 'api/users and the method GET
else if (url === '/api/users' && method === 'GET') {
res.statusCode = 200; //request was successful,
res.end('<h1>User List</h1><p>GET request received.</p>');
}
// 4. we handling 404 if is not exist
else {
res.statusCode = 404; //not found
res.end(
'<h1>404 Not Found</h1><p>The requested resource does not exist.</p>'
);
}
});
server.listen(8000, () => {
console.log('Server running at port 8000');
});
5. Loading Files
Definition
→ Serving Static Content means sending files that don't change (like your main index.html, style sheets, images, or client-side JavaScript) directly to the client.
→ For these type of files, native Node.js uses the built-in fs (File System) module to read the file's contents from the disk. It's important to set the correct Content-Type in the response header so the browser knows how to render the content (e.g., as HTML, not plain text).
Simple Code
import http from 'http';
import fs from 'fs/promises';
import { fileURLToPath } from 'url';
//__dirname gives the directory of the current module
const __dirname = fileURLToPath(new URL('.', import.meta.url));
//create server
const server = http.createServer(async (req, res) => {
if (req.url === '/') {
res.setHeader('Content-Type', 'text/html');
try {
const data = await fs.readFile(`${__dirname}index.html`, 'utf8');
res.statusCode = 200;
res.end(data);
} catch (err) {
console.error('Error loading index.html:', err);
res.statusCode = 500; // Internal Server Error
res.end('<h1>500 Server Error Loading File</h1>');
}
} else {
// ... (other routing logic)
res.statusCode = 404;
res.end('<h1>404 Not Found</h1>');
}
});
server.listen(8000, () => {
console.log('Server running at port 8000');
});
6. Building a Simple API
Definition
A well-structured API isn't just nice to look at it's intuitive to use. Think of your endpoints as the nouns (resources) in your application, and the HTTP methods as the verbs (actions).
→ When it comes to structuring your API, my advice is to use plural nouns in lowercase for your resource paths. Avoid putting verbs directly in the endpoint path, the HTTP method itself already **describes*8 the action.
For example:
Wrong: GET /get-all-users
→ Correct: GET /users
Wrong: POST /create-post
→ Correct: POST /posts
Our API should be predictable. When someone visits /users, it should clearly indicate that they're dealing with the user resource. This consistency makes our API easier to understand and more RESTful.
Simple Code
import express from 'express';
const router = express.Router();
// GET /posts
// the purpose: receiving a list of all posts
router.get('/', (req, res) => {
// Logic to fetch all posts from the database
res.json({ message: "Fetching all posts" });
});
// GET /posts/:id
// the purpose: receiving a specific post by ID
router.get('/:id', (req, res) => {
// here we extracting the resource ID from the URL
const postId = req.params.id;
// logic to fetch post 'postId'
res.json({ message: `Fetching post ${postId}` });
});
// POST /posts
// the purpose: create a new post
router.post('/', (req, res) => {
// Logic to create a new post using req.body
res.status(201).json({ message: "Post created successfully" });
});
export default router;
7. Middleware
Definition
→Middleware is basically a function that runs between the time a request reaches our server and the time a response is sent back. It has access to the request (req), the response (res), and a next() function that decides what happens next.
→If the middleware doesn’t finish the request itself, it calls next() to let the next piece of code handle it.
Simple code
import express from "express";
const app = express();
//the middleware function runs before every request handler
const logger = (req, res, next) => {
// here we log the current time, HTTP method, and requested URL
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
// we call next() to pass control to the next middleware or route
next();
};
// and we apply the middleware to all routes in the app
app.use(logger);
// Example route, handles GET requests to /users
app.get("/users", (req, res) => {
// just send a simple text response
res.send("List of users");
});
// Start the server on port 8000
app.listen(8000, () => console.log("Server running on port 8000"));
8. Cleanup (Middleware & Handlers):
→Code organization is about how we structure our project so it’s clean, readable, and easy to maintain. It means separating our code into logical parts, so that routes, middleware, and core logic don’t all live in the same file.
The logic is the following:
→ Separate responsibilities - each file or function should do one thing well.
→ Keep routes simple - no business logic directly inside routes.
→ Use folders for structure (routes/, controllers/, middleware/, etc.).
→ Handle errors properly using a global error handler.
→ Name clearly - your folder and file names should describe their purpose.
Simple code
app.js file
import express from "express";
import userRoutes from "./routes/users.js";
import { errorHandler } from "./middleware/errorHandler.js";
const app = express();
app.use(express.json());
// here we use the user routes
app.use("/api/users", userRoutes);
// here we have a global error handler
app.use(errorHandler);
app.listen(3000, () => console.log("Server running on port 3000"));
routes/users.js file
import express from "express";
import { getUsers, createUser } from "../controllers/users.js";
const router = express.Router();
// our routes should be simple, just map endpoints to controller functions
router.get("/", getUsers);
router.post("/", createUser);
export default router;
9. Get Req Body For POST
→ Handling the request body means reading the data a client sends to our server, usually when creating or updating something (like POST, PUT, or PATCH requests...).
→ This data often comes as JSON or URL-encoded form data, and we need to parse it so our code can use it.
Key Points we should understand
→ Raw data first: When the server receives a request, the body comes in as a raw stream, it’s just a bunch of bytes.
→ Parsing is required: we need middleware to turn that raw data into something usable, like a JavaScript object.
→ Content-Type matters: the client must send the correct Content-Type header (e.g., application/json) so the server knows how to parse the data.
My advice use built-in framework parsers (like express.json() in Express) early in your application's setup to automatically populate the req.body property.
Simple code
import express from "express";
const app = express();
// middleware to parse JSON bodies
app.use(express.json());
// POST route to create a new user
app.post("/users", (req, res) => {
const { name, email } = req.body; // req.body already parsed
// we us a basic validation
if (!name || !email) {
return res.status(400).json({ error: "Name and email are required" });
}
// here we simulate the creation of a new user
const newUser = { id: Date.now(), name, email };
res.status(201).json({ message: "User created!", user: newUser });
});
app.listen(3000, () => console.log("Server running on port 3000"));
→ Here express.json() parses incoming JSON requests automatically.
→ req.body now contains a normal JavaScript object.
→ By doing this, the route handler can read, validate, and use that data directly.
Conclusion ❤️
I hope this post is helpful and maybe even helps improve your understanding of Node.js fundamentals.🤗 I haven’t gone into too much detail, but I’ll be back with more Node.js content in future posts.
If you enjoyed this post, found it helpful, or it just sparked your curiosity, feel free to leave a comment or give it a reaction, I’d really appreciate it!🤗
Happy coding, and see you in the next post!


Top comments (15)
Super practical guide! You've successfully demystified core Node.js concepts like the Event Loop and Req vs Res objects with clear analogies. The RESTful API advice (using plural nouns, avoiding verbs) is a great takeaway for beginners. Excellent foundation!
Thank you! 😊 I hope it was useful for you my post!
Definitely bookmarked this and will implement soon. Sorry for being away for a while. But I would scour the entire node.js segment and learn everything. Please consider a Patreon Account you should get paid for the knowledge you five out so freely. Great work Theodora 🔥🔥🔥
Thank you for your time! 🤗Yes, it could be a solution in the future at the right time, thanks for the recommendation.🙃
As for time, don’t blame yourself, we all need breaks, it’s part of the learning process. Keep coding!
Thank you. I hope to learn from your vast expertise someday...😁
Another good one! Keep going! You are doing a great job! :)
I'm happy to hear that! Thank you so much!🥰 Keep going!🤗
That was a beautiful post to read through, thanks for all this useful information 🤗
I'm really happy to hear that! Thank you for your time!🤗
Great post! As I said on your first node.js post, I have just started working with it myself, so this is all very helpful. I will be adding thus, and the previous post, to the NotebookLM I am building to help me learn the subject
Thank you for your time! 🤗I’m glad my post was helpful to you.🥰
I wanted to focus on the fundamentals we need to pay attention to in Node.js, once you understand their purpose, you won’t really have to worry about them anymore.
So it’s completely normal to run into them at first.
Happy coding! Good luck with your learning and your project!👌🏻🤗
Wow! Amazing blogs
Thank you for your time!🤗
When it comes to building a new NodeJS I'd rather recommend Fastify over Express. It has an express-like syntax, while being faster, typescript friendly and has native async/await support 👍
Thank you for your advice! I’ll keep it in mind for the future. Since I’m a beginner in backend development, I’ve found it easier to work with Express than with other frameworks. Of course, I’ll learn others along the way. Thank you for your time! I really appreciate it!🤗