Article
Backend Engineering from Zero to Production
Learn what a backend really is, how the web talks (DNS, HTTP, cookies, CORS), then build your first Node.js and Express server with routes, controllers and middleware. One easy episode a day.
Chapter 1 ยท Oct 21, 2026
Day 1: What Is Backend, and Why Do We Need It?
Key takeaway
Frontend shows, backend decides, database remembers; never put database credentials or business rules in client code.
1. The Restaurant Analogy ๐ฝ๏ธ
You sit in the dining area, look at the menu, and tell the waiter your order. You never walk into the kitchen. The kitchen cooks, follows the rules, and fetches ingredients from the storeroom.
A web application works the same way:
| Restaurant | Web application | What it does |
|---|---|---|
| Dining area, menu, waiter | Frontend | What you see and tap |
| Kitchen | Backend | The logic and the rules |
| Storeroom | Database | Where data is kept |
You never need to know how the kitchen works to enjoy your meal, and the kitchen never lets customers walk in and cook for themselves. That separation is the whole idea of a backend.
In short: Frontend is what users see. Backend is the hidden part that does the real work.
2. Client and Server ๐ฅ๏ธ
A client is anything that asks for something, like your browser or a phone app.
A server is a computer that stays on and connected, waiting for requests and answering them.
The client sends a request, the server processes it, and the response goes back to be shown to the user.
A server isn't magic. It's just a computer running a program that listens for requests. Here is a complete one in a few lines of Node.js (we will build this properly on Day 11 and Day 12):
const http = require("http");
http.createServer((req, res) => {
res.end("Hello from the backend!");
}).listen(3000);Run it and open http://localhost:3000. Your browser is the client, this little program is the server.
Key insight: "Server" can mean the program that answers requests or the machine it runs on. Both meanings are common.
In short: Client asks, server answers. That's the request-response model, and nearly everything online uses it.
3. What Lives Where? ๐บ๏ธ
| Where | What lives there | Examples |
|---|---|---|
| Your device (frontend) | Layout and interaction | Buttons, forms, animations |
| The server (backend) | Decisions and rules | Login checks, business rules, payments, talking to the database |
| The database | Data that must be remembered | Users, posts, orders |
Here's a twist that surprises most beginners: the backend is also a client. When it needs data, it sends its own request to the database server. So one server can act as both client and server at the same time.
In short: Frontend shows, backend decides, database remembers.
4. Static vs Dynamic ๐
A static server sends stored files exactly as they are. Everyone who asks for about.html gets the same file.
A dynamic server has extra software, usually an application server and a database, that builds the page before sending it.
| Static | Dynamic | |
|---|---|---|
| What is sent | A stored file, as-is | A response built for this request |
| Same for everyone? | Yes | No, it can depend on who you are |
| Needs a backend? | No | Yes |
| Example | A portfolio page | Your Instagram feed |
Instagram can show your feed because a backend builds it for you each time. Sites like Wikipedia are mostly a few templates plus a huge database, rather than millions of separate pages written by hand.
In short: Static means the same file for everyone. Dynamic means built fresh per request, and that needs a backend.
5. Why Not Let the Frontend Talk to the Database Directly? ๐
It sounds simpler to skip the backend. Here's why we don't:
| Problem | What goes wrong without a backend |
|---|---|
| Security | Connection details stored in client code can be stolen. Anyone can open your app's code and read it. |
| Scaling | If every user connects straight to one database, it gets overwhelmed once enough people are using the app. |
| Change | With a backend in between, you can change the database structure, or even switch databases, without breaking the app. |
| Rules in one place | Otherwise business logic gets duplicated in every client, which makes updates hard. One backend can serve web, Android and iOS. |
One honest nuance: services like Firebase or Supabase do let apps talk to a database-like service. But that works because a secured gateway enforces rules on the server side, not because the browser reaches a raw database. A backend is still doing the guarding.
Key insight: Anything shipped to the user's device can be read and changed by the user. Keys and rules belong on the server.
In short: The backend is the gatekeeper. It protects data, enforces rules, and lets many apps share one system.
6. What Breaks Without This? ๐ฅ
Imagine putting this in your frontend JavaScript:
// โ Never do this
const db = connect("mongodb://admin:SuperSecret123@db.myshop.com:27017/shop");Now open the site, press Ctrl + U (View Source) or check the Sources tab in DevTools. The password is right there for everyone to read. Anyone can now connect to your database and read, change or delete everything.
The fix is the backend: the frontend calls GET /api/orders, and only the server, which nobody else can see, holds the database password.
Common mistakes
- Thinking "nobody will look at my JavaScript". Anyone can, in one click.
- Hiding a secret in an environment variable that the build tool still ships to the browser.
- Believing a request is safe because it came from your frontend. Requests can be sent by anyone.
๐งช Try it yourself
- Open any website, press F12, and go to the Network tab. Reload the page. Every line you see is the client sending a request and the server answering.
- Click one request and look at Response. That is exactly what the backend sent back.
- Open the Sources tab and browse the JavaScript files. Everything there is public, which is why secrets never go in the frontend.
๐ฏ Day 1 in one line
Frontend shows things, backend makes decisions, and the database stores data. You never trust the client with the keys.
Chapter 2 ยท Oct 22, 2026
Day 2: How Your Request Finds the Server (IP, DNS and Ports)
Key takeaway
DNS turns a domain name into an IP address (where the server is), and the port tells the server which service gets the traffic: 443 for HTTPS, 80 for HTTP. localhost always means your own machine.
You type youtube.com into your browser. How does your request find the right server, out of millions of machines on the internet?
Let's break it down.
1. IP Address: Where Is the Server? ๐
An IP address identifies a network interface and helps route data to its destination.
Think of it as a building's address. Without it, the internet would not know where to deliver your request.
142.250.190.14Your browser uses the server's IP address to communicate with it.
The example above is an IPv4 address (four numbers separated by dots). You will also see longer IPv6 addresses, such as 2001:db8::1. They do the same job, there are just far more of them available.
Key insight: Computers do not find each other by name. They find each other by IP address.
In short: An IP address tells the network where the destination is.
2. DNS: Finding the IP Address ๐
Humans remember domain names. Networks use IP addresses to route traffic.
DNS (Domain Name System) translates a domain name into its associated IP address.
youtube.com โ 142.250.190.14Think of DNS as your phone's contact list. You search for "Mom" instead of remembering her number. DNS lets you type youtube.com instead of remembering a string of digits.
| Phone contacts | DNS |
|---|---|
| Contact name ("Mom") | Domain name (youtube.com) |
| Phone number | IP address |
| Looking up the contact | DNS lookup |
DNS answers are usually cached for a while, by your browser, your operating system and the DNS resolver. That is why the lookup is often instant the second time you visit a site.
In short: DNS answers the question "Which IP belongs to this domain?"
3. Ports: Which Application Gets the Traffic? ๐ช
A single server can run multiple applications at the same time: a web server, a Node.js API, a database, and more. Ports help the operating system direct incoming traffic to the right service.
| Port | Typically used for |
|---|---|
:80 | Default HTTP port |
:443 | Default HTTPS port |
:3000 | Commonly used by Node.js development servers |
:5432 | PostgreSQL's default port |
:27017 | MongoDB's default port |
An IP address and a port are often written together:
192.168.1.10:3000- IP = Building address
- Port = Specific room inside the building
When you leave the port out of a URL, the browser uses the default. So https://example.com really means https://example.com:443.
In your own backend, you choose the port your app listens on:
import express from "express";
const app = express();
app.get("/", (req, res) => {
res.send("Hello from port 3000!");
});
app.listen(3000, () => {
console.log("Listening on http://localhost:3000");
});Key insight: The IP gets traffic to the right machine. The port gets it to the right program on that machine.
In short: A port tells the server which service should receive the traffic.
4. What Happens When You Open a Website? ๐
Let's say you visit https://example.com.
- DNS lookup: Your browser finds the IP address.
- Connection: Your device establishes a connection to the server.
- Port: The connection reaches the HTTPS service, typically on port 443.
- Request: Your browser sends an HTTP request.
- Response: The server sends back a response.
All of this usually happens in a fraction of a second.
In short: Find the IP, connect to the right port, send a request, get a response.
5. Localhost vs Public IP ๐
When developing a backend, you will often see:
http://localhost:3000localhostrefers to your own device. It usually points to the loopback address127.0.0.1(or::1in IPv6).3000is the port where your application is listening.
A public IP can make a server reachable over the internet, subject to network and firewall configuration.
| Address | Who can usually reach it | Example |
|---|---|---|
localhost | Only your own machine | http://localhost:3000 |
| Private IP | Devices on the same local network | 192.168.1.10:3000 |
| Public IP | Anyone on the internet, if the network and firewall allow it | 142.250.190.14 |
Common mistakes
- Opening
localhost:3000on your phone to reach the server on your laptop. On your phone,localhostmeans the phone itself. Use your laptop's local network IP instead (if your firewall and server settings allow it). - Forgetting the port.
http://localhostgoes to port 80, not to your app on port 3000. - Two apps on one port. Only one program can listen on a given port at a time. If you see an error like
EADDRINUSE, something is already using that port. - Thinking a domain is the server. The domain is just a name. DNS points it to an IP, and the server lives at that IP.
6. The Big Picture ๐ง
| Concept | The question it answers |
|---|---|
| ๐ IP | Where is the destination? |
| ๐ DNS | Which IP belongs to this domain? |
| ๐ช Port | Which service should receive the traffic? |
| ๐ localhost | "This same machine" |
That's the foundation of how your backend becomes reachable over a network.
๐งช Try it yourself
-
Do a DNS lookup in your terminal and see the IP addresses behind a domain:
bashnslookup youtube.comYou may see several IPs. Large sites use many servers.
-
Start the Express server above, then open
http://localhost:3000in your browser. Now tryhttp://localhost:3001. Nothing is listening there, so the connection fails. -
Watch a full request with curl. The
-vflag shows the connection to the IP and port, then the request and response:bashcurl -v https://example.com
๐ฏ Day 2 in one line
DNS turns a domain name into an IP address, the IP address tells you where the server is, and the port tells you which service on that server should receive your request.
Chapter 3 ยท Oct 23, 2026
Day 3: Frontend vs Backend and Why You Never Trust the Client
Key takeaway
The client sends only IDs and quantities; the backend looks up prices, checks permissions and verifies identity on every request.
1. The Shop With Self-Written Price Tags ๐ท๏ธ
Imagine a shop where customers write the price on an item themselves, and the cashier charges whatever the tag says.
Someone picks up a laptop, writes โน1, and walks away with it.
Absurd? That's exactly what happens when a backend trusts the price sent by the frontend.
In short: The backend must check the real price, never trust the customer's tag.
2. What the Frontend Is Responsible For ๐จ
The frontend runs on the user's device. Its job is to provide the experience:
- Layout, design and animations
- Clicks, forms and navigation
- Instant feedback, like "Invalid email"
- Managing local UI state (is this menu open? which tab is selected?)
- Sending requests to the backend and displaying the results
Frontend validation improves usability, but it does not guarantee security. It's there to help honest users, not to stop dishonest ones.
In short: The frontend makes things convenient. It cannot enforce security on its own.
3. What the Backend Is Responsible For โ๏ธ
The backend runs on a server you control. Its job is correctness, security and business logic:
| Responsibility | What it means | Example |
|---|---|---|
| Validation | Reject invalid data | Quantity must be a positive number |
| Authentication | Establish who the user is | Verify the session or token |
| Authorization | Decide what the user can access | Only admins can delete products |
| Business rules | Calculate prices, apply discounts, check stock | Total = price ร quantity โ discount |
| Data access | Read and update the database | Save the order |
| Secrets | Protect API keys and database credentials | The payment gateway secret key |
| Protection | Apply rate limits and record relevant events | Max 5 login attempts per minute |
In short: The backend enforces the rules that matter.
4. Why You Can Never Trust the Client ๐ซ
Everything running on a user's device is under their control:
- Browser DevTools can inspect frontend code.
- The Network tab exposes every request and response.
- Requests can be replayed or modified using external tools.
- Anyone can call a publicly reachable API without using your frontend at all.
An attacker does not need to use your nice checkout form. They can send a request directly to your server.
This line between the untrusted client and your server is called the trust boundary. Every request that crosses it must be treated as untrusted input.
Key insight: Your backend can't tell whether a request came from your beautiful UI or from someone typing it by hand. So it must check everything, every time.
In short: Never assume a request is safe just because your frontend generated it.
5. Four Things You Must Never Trust the Client For ๐
A. Validation
The frontend checks whether an email looks valid. The backend validates it again before processing the request. Client-side checks can be bypassed, so security must not depend on them.
B. Prices and money
Never trust a price or discount supplied by the client.
The client should send the product ID and quantity. The backend retrieves the authoritative price and calculates the total.
C. Permissions
Hiding an Admin button does not prevent someone from calling an admin API directly. The backend must check permissions on every relevant request and deny access by default.
D. Identity
A request claiming userId: 42 or role: "admin" proves nothing.
The backend must verify the user's session or token and establish their identity and permissions using trusted server-side data.
In short: Validate data, calculate money, enforce permissions and establish identity on the server.
6. Secrets Never Belong in the Frontend ๐
Frontend code is delivered to the user's device, so anything embedded in it can potentially be inspected.
For example, Vite exposes variables prefixed with VITE_ to client-side code:
# .env in a Vite project
VITE_API_URL=https://api.myshop.com # โ
fine, this is public anyway
VITE_STRIPE_SECRET=sk_live_... # โ shipped to every visitor's browserNever put database passwords, private API keys or signing secrets in those variables. If the frontend needs a secret operation, it should call your backend, which uses the secret securely.
In short: If a secret reaches the browser, treat it as exposed.
7. Who Decides What? โ๏ธ
| Concern | Frontend | Backend |
|---|---|---|
| Validation | Gives instant feedback | Validates before processing |
| Prices | Displays them | Calculates the actual total |
| Permissions | Shows or hides buttons | Enforces access on every request |
| Identity | Displays the logged-in user | Verifies who they are |
| Secrets | Never exposes private keys | Protects and uses them |
The rule: The frontend can guide the user, but only the backend can enforce trusted decisions.
8. What Breaks Without This? ๐ฅ
Imagine your checkout request contains:
{ "productId": "laptop123", "quantity": 1, "price": 1299.99 }A user opens DevTools, changes the price to 1 and resends the request. If the backend blindly trusts that value, the order could be processed at the wrong price.
The correct approach:
- Receive only the product ID and quantity.
- Retrieve the current authoritative price from trusted server-side data.
- Validate the quantity and check stock.
- Calculate the total on the server.
- Process the order using the validated values.
Here's what that looks like in Express:
app.post("/checkout", requireLogin, async (req, res) => {
const { productId, quantity } = req.body; // ignore any "price" the client sends
if (!Number.isInteger(quantity) || quantity < 1) {
return res.status(400).json({ error: "Invalid quantity" });
}
const product = await Product.findById(productId); // trusted price
if (!product || product.stock < quantity) {
return res.status(409).json({ error: "Not available" });
}
const total = product.price * quantity; // calculated on the server
const order = await Order.create({ userId: req.user.id, productId, quantity, total });
res.status(201).json(order);
});The same principle applies to discounts, permissions and user identity.
Common mistakes
- Sending
price,discountorisAdminfrom the frontend and using it as-is. - Checking permissions only in the UI ("the button is hidden, so it's safe").
- Reading
userIdfrom the request body instead of from the verified session or token.
๐งช Try it yourself
- Open any shop or form on a site you own, press F12 โ Network, and submit something.
- Right-click the request โ Copy as cURL. Paste it in a terminal and change a value before sending.
- Notice that the server can't tell the difference. That's why every check must live on the backend.
๐ฏ Day 3 in one line
The frontend provides the experience. The backend enforces the rules. Never trust the client to make decisions that affect security, money or data.
Chapter 4 ยท Oct 24, 2026
Day 4: How Frontend and Backend Communicate (HTTP, Polling, SSE, WebSockets, Webhooks, gRPC)
Key takeaway
Start with plain HTTP. Use SSE for server-to-client streams, WebSockets for two-way real-time chat, and verified, duplicate-safe webhooks for events from other services, such as payment confirmations that must not depend on the browser.
How does a chat message appear instantly? How does Razorpay notify your backend that a payment succeeded?
Different problems need different ways to communicate. Let's understand them.
1. The Ways to Communicate ๐ฃ๏ธ
Think of communication like everyday life:
| Method | Everyday analogy |
|---|---|
| HTTP | Ordering at a counter. Ask, get an answer. |
| Polling | Asking repeatedly, "Is it ready yet?" |
| Long polling | "Stay on the line. I'll tell you when it's ready." |
| SSE | Listening to a radio. The server sends updates. |
| WebSockets | A phone call. Both sides can talk anytime. |
| Webhooks | A doorbell. Another service notifies your server when something happens. |
| gRPC | Two departments using a shared, efficient language. |
Each solves a different communication problem.
In short: There is no universal best method. Choose based on who needs to communicate and how frequently.
2. HTTP: The Default ๐งพ
Most web interactions follow a simple pattern:
- The client sends a request.
- The server processes it.
- The server returns a response.
Example: You open a product page, and your backend returns the product details.
The exchange is straightforward, but the server cannot use an ordinary completed HTTP request to spontaneously deliver a new message to the browser.
What if the client needs updates without refreshing? That's where other techniques help.
In short: HTTP request and response is the foundation of most web communication.
3. Polling vs Long Polling ๐
Imagine tracking a food delivery order.
Polling: Your app asks the server every 2 seconds, "Where is my order?"
setInterval(async () => {
const res = await fetch("/api/orders/123/status");
const { status } = await res.json();
console.log("Order status:", status);
}, 2000);If 1,000 clients do this, they can generate around 500 requests per second, even when nothing changes.
Long polling: Your app asks once, and the server holds the request until an update is available or a timeout occurs. The client then makes another request.
This reduces repeated empty responses, but holding many requests open still consumes server resources.
Use cases: Basic live dashboards, delivery tracking, or environments where persistent connections are unavailable.
In short: Polling repeatedly asks. Long polling waits for an answer before asking again.
4. SSE: The Radio ๐ป
Server-Sent Events (SSE) allow a server to send a continuous stream of events to a browser over HTTP.
The client opens a connection. The server sends updates whenever something changes.
- Direction: Server to client
- Supports automatic reconnection in browsers using
EventSource - Best for notifications, live feeds, progress updates and AI text streaming
Example: An AI response appears word by word as the server generates it.
// Browser
const events = new EventSource("/api/notifications/stream");
events.onmessage = (event) => console.log("Update:", event.data);
// Server (Express)
app.get("/api/notifications/stream", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.flushHeaders();
const timer = setInterval(() => {
res.write(`data: ${new Date().toISOString()}\n\n`);
}, 1000);
req.on("close", () => clearInterval(timer));
});The client can still send separate HTTP requests, but the SSE connection itself is one-way.
In short: Use SSE when the server needs to push updates and the client rarely needs to send messages through the same connection.
5. WebSockets: The Phone Call ๐
WebSockets provide a persistent, two-way communication channel. After the connection is established, both client and server can send messages independently.
- Direction: Both ways
- Low overhead for repeated messages
- Supports text and binary data
- Requires connection management and reconnection logic
Example: In a chat app, you send a message and receive incoming messages without repeatedly requesting updates.
const socket = new WebSocket("wss://chat.example.com");
socket.addEventListener("open", () => socket.send("Hello!"));
socket.addEventListener("message", (event) => {
console.log("New message:", event.data);
});| SSE | WebSockets | |
|---|---|---|
| Direction | Server to client | Both ways |
| Reconnection | Automatic with EventSource | You write it yourself |
| Data | Text | Text and binary |
| Great for | Feeds, notifications, AI streaming | Chat, multiplayer games, collaboration |
In short: Use WebSockets when both sides need frequent, real-time communication.
6. Webhooks: The Doorbell ๐
Webhooks are different. They typically let one service notify another server about an event.
Example: A customer pays through Razorpay.
- Your backend creates a payment order.
- The payment provider processes the payment.
- The provider sends an event to your webhook URL.
- Your backend verifies the webhook signature.
- Your backend safely updates the order status.
Even if the customer closes the browser, the provider can still notify your server.
Important: Webhooks can be retried or delivered more than once. Your handler must verify authenticity and safely handle duplicate events. Processing the same event twice must be harmless.
In short: Webhooks let services notify your backend when something happens, without your backend repeatedly asking.
7. gRPC: Service to Service โ๏ธ
gRPC is a framework commonly used for communication between backend services.
It uses Protocol Buffers by default to define structured messages and serialize them efficiently. It commonly runs over HTTP/2 and supports streaming.
Example: An order service communicates with an inventory service to check stock. Both sides share a .proto file that defines exactly what a stock request and reply look like.
gRPC is particularly useful when services need strongly defined contracts and efficient communication. Browsers generally need a compatible intermediary such as gRPC-Web.
In short: gRPC is a strong option for structured, efficient communication between backend services.
8. Which One Should You Use? ๐งญ
| What you need | Use |
|---|---|
| Normal API calls and form submissions | HTTP |
| Repeated checks for updates | Polling |
| Waiting for updates over ordinary HTTP | Long polling |
| Server-to-client live updates | SSE |
| Two-way real-time communication | WebSockets |
| Notifications between external services | Webhooks |
| Efficient backend-to-backend communication | gRPC |
Key insight: Start with ordinary HTTP. Add another communication method only when your requirements justify it.
Common mistakes
- Using WebSockets for everything. This is the most common beginner mistake. A product page does not need a live connection.
- Skipping webhook signature checks. Anyone can send a POST to a public URL.
- Not handling duplicate webhooks. The same event can arrive twice, and must not ship two orders.
9. What Breaks Without the Right Approach? ๐ฅ
Imagine a chat app built only with normal HTTP requests. A new message might not appear until the user refreshes or requests updates again.
Or imagine a payment system that relies on the browser to tell the backend that payment succeeded. The customer closes the tab, and the notification never arrives.
The solution is to choose the correct communication pattern. For payment confirmation, a verified webhook can notify your backend independently of the browser.
๐งช Try it yourself
-
Stream SSE in the terminal. Run the SSE server above, then:
bashcurl -N http://localhost:3000/api/notifications/stream-Nturns off buffering, so events appear live. -
Spot a WebSocket. On a web app with live chat, filter the Network tab to WebSocket connections and click one to see its messages.
๐ฏ Day 4 in one line
HTTP handles request and response. Polling and streaming techniques deliver updates. WebSockets enable two-way real-time communication. Webhooks notify servers about events. gRPC connects services efficiently.
Chapter 5 ยท Oct 25, 2026
Day 5: Monolith vs Microservices
Key takeaway
A monolith is one deployable app; microservices are independently deployable services that talk over a network and bring distributed-system complexity. Start with a well-organized modular monolith and split only for a concrete reason.
Imagine you're building an e-commerce application.
Should everything live in one application, or should you split it into multiple independent services?
Let's understand the trade-offs.
1. The Single Kitchen: Monolith ๐ณ
Imagine a restaurant where one kitchen handles everything: orders, cooking, billing and inventory.
A monolithic backend works similarly. Different features live inside one application, which is built and deployed as a unit.
For example, your e-commerce backend might contain:
- Authentication
- Products
- Cart
- Orders
- Payments
All these modules run as part of the same application.
In short: One application, multiple responsibilities.
2. Separate Kitchens: Microservices ๐ช
Now imagine a restaurant chain where separate teams handle billing, inventory and orders through independently operated systems.
With microservices, an application is split into smaller services. Each service owns a specific business capability and can be developed and deployed independently.
An e-commerce system might have:
- Auth Service
- Product Service
- Order Service
- Payment Service
- Notification Service
These services communicate over a network, and each can have its own codebase and data ownership.
In short: Multiple independent services, each responsible for a specific capability.
3. What's the Actual Difference? โ๏ธ
| Monolith | Microservices | |
|---|---|---|
| Deployment | One application to build and deploy | Services can be deployed independently |
| Development | Simpler local development and debugging | Monitoring, testing and operations become more complex |
| Communication | Internal function calls can connect modules directly | Network communication introduces latency and failure possibilities |
| Release risk | A deployment can affect the entire application | A release usually touches one service |
| Teams | Everyone works in one codebase | Teams can work on different services |
| Scaling | Run more copies of the whole app | Individual services can scale according to demand |
| Main danger | Poorly separated modules can become difficult to maintain | Distributed-system complexity |
The communication row is the big one. Compare:
// Monolith: a direct function call inside the same process
const stock = await inventory.checkStock(productId);// Microservices: a network request that can be slow or fail
const res = await fetch(`http://inventory-service/stock/${productId}`, {
signal: AbortSignal.timeout(2000), // give up after 2 seconds
});
if (!res.ok) throw new Error("Inventory service unavailable");
const stock = await res.json();The second version must handle timeouts, errors and a service that is down. The first one doesn't.
In short: Microservices provide flexibility, but they also introduce distributed-system complexity.
4. What Happens When Traffic Increases? ๐
Suppose your e-commerce application receives 100,000 requests, and most of them are for product browsing.
- In a monolith, you can run multiple instances of the application, but each instance generally contains all its modules. You may end up scaling more of the application than necessary.
- With microservices, you could scale the Product Service independently from the Order Service.
For example:
| Monolith | Microservices | |
|---|---|---|
| To handle extra browsing | 5 copies of the whole app | 5 copies of the Product Service |
| Order handling | Comes along in every copy | Still 1 copy of the Order Service |
But there's a catch: independent scaling is useful only when the architecture and workload justify the extra complexity.
In short: Microservices can scale individual capabilities independently. A monolith can also scale by running multiple instances.
5. What Happens When Something Fails? ๐งฏ
Imagine the Notification Service goes down while a customer places an order.
In a well-designed microservices architecture, the order may still succeed even if sending the notification fails.
However, this isolation does not happen automatically. Services must be designed to handle failures, timeouts and retries correctly. In code, that means deciding which steps are critical and which are not:
const order = await saveOrder(input); // critical: must succeed
try {
await sendConfirmation(order); // not critical
} catch (err) {
console.error("Notification failed, will retry later:", err.message);
}
res.status(201).json(order);In a monolith, a notification-related bug could also affect the application if it disrupts shared resources or crashes the process. But a well-designed monolith can isolate failures too.
In short: Architecture alone does not guarantee reliability. How you design failure handling matters.
6. When Should You Choose Each? ๐งญ
Choose a monolith when:
- You're building your first product or startup
- The team is small
- Requirements are still changing
- You want simpler development and deployment
- You don't yet have a clear reason to split services
Consider microservices when:
- Different business capabilities need independent scaling
- Multiple teams need independent ownership and releases
- The system has clear service boundaries
- You can support distributed tracing, monitoring and failure handling
- The benefits outweigh the operational cost
In short: Start simple. Split services when you have a concrete reason, not just because the architecture looks impressive.
7. Monolith Doesn't Mean Messy ๐งฉ
A common misconception: a monolith does not have to mean messy code.
You can build a modular monolith, where authentication, products, orders and payments are separated into well-defined modules but deployed as one application.
shop-backend/
src/
auth/
products/
cart/
orders/
payments/
server.js โ one app, one deploymentEach module exposes a small public interface, and other modules use only that:
// orders/index.js: the only door into the orders module
export { createOrder, getOrder } from "./orderService.js";This gives you simpler operations while keeping the code organized. If a module later needs to become an independent service, a clear boundary makes that transition easier.
In short: Good architecture starts with clear boundaries, not with more servers.
Common mistakes
- Starting with microservices for a small app "because big companies do it".
- Splitting without clear boundaries. Services that must call each other for every request give you the costs of both designs.
- Assuming microservices are automatically more reliable. Without timeouts and retries, one slow service can drag down others.
- Letting a monolith turn into a ball of mud. Any module reaching into any other module's internals makes change painful.
๐งช Try it yourself
Feel the difference between a function call and a network call:
-
Save this as
inventory.jsand run it withnode inventory.js(in a project with Express installed and"type": "module"inpackage.json):jsimport express from "express"; const app = express(); app.get("/stock/:id", (req, res) => res.json({ id: req.params.id, available: 5 })); app.listen(3001, () => console.log("Inventory service on port 3001")); -
Call your tiny "service" from another terminal:
bashcurl http://localhost:3001/stock/42 -
Stop the service with
Ctrl+Cand run the same curl again. You get a connection error. That is the kind of failure every microservice has to plan for, and a direct function call inside a monolith never sees.
๐ฏ Day 5 in one line
A monolith is one deployable application. Microservices are independently deployable services. Start with the simplest architecture that meets your needs.
Phase 1 complete! ๐ฏ You now know how the web fits together: clients and servers, how requests find a server, who decides what, how frontends and backends communicate, and how a backend can be structured. Phase 2 begins next.
Chapter 6 ยท Oct 26, 2026
Day 6: HTTP Methods, URLs, Path and Query Parameters
Key takeaway
The method says what action to take, the path says which resource, path parameters pick a specific resource, and query parameters filter, sort or paginate. Both arrive as strings, so validate them and check access.
You call an API, but how does the backend know what resource you want and what action you're trying to perform?
Let's break down the basics.
1. Anatomy of a URL ๐งญ
Consider this API URL:
https://api.shop.com/products/42?currency=INRHere's what each part means:
| Part | Value | Meaning |
|---|---|---|
| Scheme | https | How to talk to the server (secure HTTP) |
| Host | api.shop.com | Which server to send the request to |
| Path | /products/42 | Which resource on that server |
| Query parameter | ?currency=INR | Extra details about what you want |
Notice there is no port in this URL. As you saw in Day 2, https uses port 443 by default.
The URL tells the client where to send the request and identifies the resource or information being requested.
In short: A URL identifies the destination and the requested resource.
2. HTTP Methods: What Action Do You Want? ๐ ๏ธ
The same resource can support different operations. Imagine /products/42:
| Method | What it does | Example |
|---|---|---|
GET | Retrieves the resource | GET /products/42 โ fetch product 42 |
POST | Submits data, commonly to create a resource | POST /products โ create a new product |
PUT | Replaces a resource's representation | PUT /products/42 โ replace product 42 with the data sent |
PATCH | Partially updates a resource | PATCH /products/42 โ update some fields of product 42 |
DELETE | Requests deletion of a resource | DELETE /products/42 โ delete product 42 |
Here is what a real PATCH request looks like on the wire:
PATCH /products/42 HTTP/1.1
Host: api.shop.com
Content-Type: application/json
{ "price": 1199 }Only the price is sent. With PUT, you would send the whole product, because PUT replaces it.
The method and path work together to describe the operation. In Express, each method gets its own route:
app.get("/products/:id", getProduct);
app.patch("/products/:id", updateProduct);
app.delete("/products/:id", deleteProduct);Key insight: Same path, different method, completely different action.
In short: The path identifies the resource. The method describes the intended operation.
3. Path Parameters: Which Specific Resource? ๐ฏ
Path parameters identify a particular resource or a position within a resource hierarchy.
GET /users/17Fetch user 17.
GET /users/17/orders/92Fetch order 92 belonging to user 17.
In Express, you can access them using req.params. You define the route with a placeholder:
/products/:productIdFor /products/42, the backend receives:
req.params.productId; // "42" (a string, not a number)Remember, a path parameter is just input. The backend must still validate it and check whether the user is allowed to access that resource.
app.get("/products/:productId", async (req, res) => {
const id = Number(req.params.productId);
if (!Number.isInteger(id) || id < 1) {
return res.status(400).json({ error: "Invalid product ID" });
}
const product = await findProduct(id);
if (!product) {
return res.status(404).json({ error: "Product not found" });
}
res.json(product);
});In short: Path parameters identify which resource the request refers to.
4. Query Parameters: How Should Results Be Selected? ๐
Query parameters commonly control filtering, searching, sorting and pagination. They come after the ?, and each pair is separated by &.
GET /products?category=phones&sort=price&page=2This could mean:
- Show products in the
phonescategory. - Sort by
price. - Return page
2.
In Express, access them through req.query:
req.query.category; // "phones"
req.query.page; // "2"Query values usually arrive as strings, so validate and convert them when necessary. A query value can even be missing, or arrive as an array if the same key is repeated.
app.get("/products", (req, res) => {
const page = Number(req.query.page ?? 1); // "2" โ 2, missing โ 1
if (!Number.isInteger(page) || page < 1) {
return res.status(400).json({ error: "page must be a positive whole number" });
}
// ...find products for this page
});| Path parameter | Query parameter | |
|---|---|---|
| Looks like | /products/42 | /products?page=2 |
| Answers | Which resource? | Which results and how? |
| Typical use | IDs, nested resources | Filters, search, sorting, pagination |
| In Express | req.params | req.query |
In short: Path parameters identify a resource. Query parameters commonly control which results you want and how they are returned.
5. Can We Use Both Together? ๐ค
Absolutely. Consider:
GET /users/17/orders?status=delivered&page=2| Part | What it does |
|---|---|
users/17 | Identifies the user |
orders | Identifies the resource collection |
status=delivered | Filters the orders |
page=2 | Selects a page of results |
app.get("/users/:userId/orders", (req, res) => {
const { userId } = req.params; // "17"
const { status, page } = req.query; // "delivered", "2"
// validate, check access, then query the database
});One request, multiple pieces of information.
6. A Common Beginner Mistake โ ๏ธ
These two URLs are not equivalent:
GET /products/42
GET /products?id=42The first uses a path parameter. The second uses a query parameter.
Both designs can be valid, but your backend must define and implement the expected route. In Express, routes match on the path only, so /products?id=42 is handled by app.get("/products"), not by app.get("/products/:id").
Also, never assume that a valid-looking ID grants access to the corresponding resource. Changing /users/17/orders to /users/18/orders must not show someone else's orders.
Common mistakes
- Treating params as numbers.
req.params.idandreq.query.pageare strings."2" + 1is"21", not3. - Skipping validation because "the frontend only sends valid IDs". Anyone can type any URL (remember Day 3).
- Skipping the access check. A valid ID only says which resource, not who is allowed to see it.
- Using GET to change data.
GETshould retrieve. UsePOST,PUT,PATCHorDELETEfor changes. - Mixing up PUT and PATCH.
PUTreplaces the whole thing.PATCHupdates only the fields you send.
๐งช Try it yourself
JSONPlaceholder is a free fake API for practice. It pretends to save changes but never actually does.
-
Path parameter, fetch one post:
bashcurl https://jsonplaceholder.typicode.com/posts/1 -
Query parameter, filter posts by user:
bashcurl "https://jsonplaceholder.typicode.com/posts?userId=1" -
A different method on the same path:
bashcurl -X PATCH https://jsonplaceholder.typicode.com/posts/1 \ -H "Content-Type: application/json" \ -d '{"title":"Updated title"}'
Notice the quotes around the URL in step 2. Without them, your shell may treat ? or & as special characters.
๐ฏ Day 6 in one line
HTTP methods describe the intended action, paths identify resources, path parameters specify particular resources, and query parameters refine the request.
Chapter 7 ยท Oct 27, 2026
Day 7: HTTP Headers, Request Body and Content Types
Key takeaway
Headers are labels that describe the message, the body carries the data, and Content-Type must match the body's real format. Accept says which response formats the client can handle. Always validate the body, even when it parses correctly.
When your frontend sends data to a backend, how does the server know what the data means and how to interpret it?
Let's break it down.
1. HTTP Headers: Labels on a Package ๐ท๏ธ
Headers carry metadata about an HTTP request or response.
Think of them as a label attached to a package. They describe the package, but they aren't the contents inside it.
Common headers
| Header | What it tells the receiver | Example value |
|---|---|---|
Content-Type | Format of the message body | application/json |
Accept | Response formats the client can handle | application/json |
Authorization | Credentials used to authenticate a request | Bearer <token> |
Cache-Control | Instructions related to caching | no-cache |
Headers can appear in both requests and responses.
Example:
Content-Type: application/jsonThis tells the receiver that the body contains JSON data.
What a full request looks like
Here is a raw HTTP request with its headers and body. A blank line separates the headers from the body:
POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
{"name": "D3", "email": "d3@example.com"}Header names are case-insensitive, so content-type and Content-Type mean the same thing.
In short: Headers describe a message and provide information needed to interpret or handle it.
2. Request Body: The Actual Data ๐ฆ
When creating a user, placing an order, or updating a profile, the client often sends data in the request body.
Example:
POST /users
Content-Type: application/json
{
"name": "D3",
"email": "d3@example.com"
}The backend receives this data and processes it according to the application's rules.
Not every request needs a body. A typical GET request retrieves data without one.
| Method | Usually has a body? | Example |
|---|---|---|
GET | No | Fetch a product |
POST | Yes | Create a user |
PUT / PATCH | Yes | Update a profile |
DELETE | Usually no | Remove a cart item |
In Express, you turn on a body parser so the JSON body appears on req.body:
import express from "express";
const app = express();
app.use(express.json()); // parses bodies sent as application/json
app.post("/users", (req, res) => {
const { name, email } = req.body;
// Validate before using this data!
res.status(201).json({ name, email });
});In short: The request body carries the data being submitted to the server.
3. Content-Type: What Format Is the Body In? ๐
The same information can be represented in different formats.
JSON
Content-Type: application/json
{
"productId": "p123",
"quantity": 2
}URL-encoded form data
Content-Type: application/x-www-form-urlencoded
productId=p123&quantity=2Multipart form data
Content-Type: multipart/form-dataCommonly used when submitting form fields along with files, such as a profile picture. In a real request, this header also includes a boundary=... value that separates the parts. Browsers and HTTP libraries add it for you.
Comparing the three
| Content-Type | Body looks like | Typical use | Express parser |
|---|---|---|---|
application/json | {"quantity": 2} | APIs and fetch calls | express.json() |
application/x-www-form-urlencoded | productId=p123&quantity=2 | Classic HTML forms | express.urlencoded() |
multipart/form-data | Several parts split by a boundary | File uploads with fields | A library such as multer |
These formats aren't interchangeable. The server needs to interpret the body according to its actual format.
In short: Content-Type tells the receiver how the message body is represented.
4. When the Content-Type Is Wrong โ
Imagine sending this body, but declaring the wrong type:
POST /cart
Content-Type: text/plain
{
"quantity": 2
}The server may treat the body as plain text instead of parsing it as JSON.
Depending on the server's implementation, the expected data may not be available in the format the application needs, or the request may be rejected. For example, express.json() only parses JSON content types, so req.body.quantity will not be there. Some APIs reject the request with 415 Unsupported Media Type.
In short: The body format and Content-Type must agree, and the server must support that format.
5. Content-Type vs Accept ๐
These two headers are easy to confuse.
| Header | Question it answers | It is about |
|---|---|---|
Content-Type | What format is this message body in? | The data being sent |
Accept | What response formats can the client handle? | The response the client wants back |
Example:
POST /users
Content-Type: application/json
Accept: application/jsonThe client sends JSON and indicates that it can accept a JSON response.
Content-Type also appears on responses. When the server replies, it uses Content-Type to describe the body it is sending back.
Key insight: One describes the data being sent. The other expresses a preference for the response format.
6. Putting It All Together ๐
Imagine adding a product to your cart.
Request:
POST /cart
Content-Type: application/json
Accept: application/json
{
"productId": "p123",
"quantity": 2
}Here's what each part contributes:
| Part | What it contributes |
|---|---|
| Method and URL | Identify the operation and destination |
| Headers | Describe the message and its expected handling |
| Body | Carries the product ID and quantity |
This is how the frontend would send that request with fetch:
const res = await fetch("/cart", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({ productId: "p123", quantity: 2 }),
});Key insight: The backend must still validate the incoming data. Correct formatting does not make the data trustworthy.
Common mistakes
- Passing a plain object as the
fetchbody withoutJSON.stringify(). The browser sends the text[object Object]instead of JSON. - Declaring
Content-Type: application/jsonbut sending form data, or the other way round. - Setting
Content-Typeby hand when uploadingFormData. This drops the boundary, so let the browser set it. - Forgetting the body parser (
express.json()), then wondering whyreq.bodyis empty. - Mixing up
Content-Type(what I'm sending) andAccept(what I want back). - Trusting the body just because it parsed correctly.
๐งช Try it yourself
-
Look at real response headers. The
-iflag shows them above the body:bashcurl -i https://example.comFind the
Content-Typeline. For a web page, it will be something liketext/html. -
Send JSON to an echo service that shows what it received:
bashcurl -X POST https://httpbin.org/post \ -H "Content-Type: application/json" \ -d '{"productId": "p123", "quantity": 2}'The
jsonfield in the reply shows your parsed body. Now remove the-Hline and run it again. Without it, curl labels the body asapplication/x-www-form-urlencodedby default, so the server tries to read your JSON as form data. Compare theformandjsonfields in the two replies. -
Open DevTools, go to the Network tab, and click any request. Under Headers, compare the request headers with the response headers.
๐ฏ Day 7 in one line
Headers describe the message, the body carries the data, and Content-Type tells the receiver how to interpret it.
Chapter 8 ยท Oct 28, 2026
Day 8: HTTP Status Codes Explained
Key takeaway
Return the status code that describes what actually happened: 2xx success, 3xx redirection, 4xx a problem with the request (401 not authenticated, 403 not allowed), 5xx a server or upstream failure. Never return 200 for errors.
Your API returns 404, 401, or 500.
What do these numbers actually mean, and when should your backend return each one?
Let's break it down.
1. What Are HTTP Status Codes? ๐ข
An HTTP status code is a three-digit number sent by the server to indicate the result of a request.
It is the very first thing in a response, on the status line:
HTTP/1.1 404 Not Found
Content-Type: application/json
{"error": "Product not found"}Status codes are grouped into five categories:
| Range | Category | What it means |
|---|---|---|
1xx | Informational | Interim information before the final response |
2xx | Success | The request worked |
3xx | Redirection | Look somewhere else, or reuse what you have |
4xx | Client error | Something is wrong with the request or the client's permissions |
5xx | Server error | The server or something it depends on failed |
As a backend developer, you'll mostly work with 2xx, 4xx, and 5xx.
In short: Status codes tell the client how the server handled a request.
2. 2xx: Success โ
The request succeeded.
| Code | Name | When to use | Example |
|---|---|---|---|
200 | OK | The request succeeded | GET /products/42 |
201 | Created | A resource was created | POST /users |
202 | Accepted | The request was accepted for processing, but processing may not be complete | POST /reports that starts a background job |
204 | No Content | The request succeeded, and there is no response body | DELETE /cart/items/42 |
A successful request does not always mean the server returns data. 204 is a good example.
In Express, you set the code with res.status():
app.post("/users", async (req, res) => {
const user = await createUser(req.body);
res.status(201).json(user); // a user was created
});
app.delete("/cart/items/:id", async (req, res) => {
await removeItem(req.params.id);
res.status(204).end(); // success, nothing to send back
});3. 3xx: Redirection ๐
The client needs to follow a redirect or use a different location.
| Code | Name | When to use | Example |
|---|---|---|---|
301 | Moved Permanently | The resource has permanently moved | An old blog URL now lives at a new address |
302 | Found | A temporary redirect | Send the user to a login page for now |
304 | Not Modified | The cached resource is still valid, so the client can reuse it | Browser re-checks a cached image that hasn't changed |
Example: A browser requests an old URL, and the server redirects it to a new URL. The new address goes in the Location header:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-pageIn short: Redirection helps clients find resources or reuse cached responses.
4. 4xx: Client Errors โ ๏ธ
The request cannot be fulfilled because of something about the request or the client's permissions.
| Code | Name | When to use | Example |
|---|---|---|---|
400 | Bad Request | The request is malformed or invalid | Invalid JSON |
401 | Unauthorized | Valid authentication is missing or has failed | Missing or invalid login credentials |
403 | Forbidden | The server refuses access | A user without permission tries to access an admin resource |
404 | Not Found | The requested resource was not found | A product ID does not exist |
409 | Conflict | The request conflicts with the current state of the resource | An email is already registered |
422 | Unprocessable Content | The content is understood, but its instructions cannot be processed | Valid JSON, but the values break your rules |
429 | Too Many Requests | The client has sent too many requests | Too many requests in a short period |
401 vs 403: the classic mix-up
| 401 Unauthorized | 403 Forbidden | |
|---|---|---|
| The server is saying | "I don't know who you are." | "I know who you are, but you can't do this." |
| Typical cause | No token, expired session, wrong password | Logged in, but not an admin |
Think of an office building. No badge at the door is a 401. A valid badge that doesn't open the server room is a 403.
In short: 4xx codes indicate that the request cannot be fulfilled as submitted. The exact code depends on the situation.
5. 5xx: Server Errors ๐ฅ
The server failed to fulfil an otherwise valid request.
| Code | Name | When it happens | Example |
|---|---|---|---|
500 | Internal Server Error | An unexpected server-side failure | An unhandled exception in your code |
502 | Bad Gateway | A gateway or proxy received an invalid response from an upstream server | Your app crashed behind a proxy such as Nginx |
503 | Service Unavailable | The server is temporarily unable to handle the request | The server is overloaded or down for maintenance |
504 | Gateway Timeout | A gateway or proxy did not receive an upstream response in time | See the diagram below |
Example: Your API depends on another service, which fails to respond before the gateway's timeout. The client receives 504 Gateway Timeout.
In short: 5xx codes indicate a server-side or upstream failure, not necessarily a bug in your own application code.
6. Which Status Code Should I Return? ๐งญ
A simplified guide for an endpoint that requires login. Real APIs may order these checks differently, and any unexpected failure becomes a 500.
Two more to keep in mind: 409 Conflict when the request clashes with existing data (like a duplicate email), and 429 Too Many Requests when a client hits your rate limit.
7. Common Mistakes Backend Developers Make ๐ซ
- Returning
200 OKfor every outcome, including failures. - Using
401when the user is authenticated but lacks permission. Usually, that calls for403. - Returning
500for ordinary validation errors that should be represented by an appropriate 4xx code. - Returning
201 Createdwhen no resource was created. - Returning detailed stack traces or internal error information to clients.
// BAD: "success" for a failure, and internals leaked to the client
res.status(200).json({ success: false, error: "User not found" });
res.status(500).json({ error: err.stack });
// GOOD: honest status codes, details logged on the server only
res.status(404).json({ error: "User not found" });
console.error(err);
res.status(500).json({ error: "Something went wrong" });Choose a status code that accurately represents the outcome.
8. A Real API Example: Registration ๐
Imagine a registration endpoint: POST /users.
| Situation | Status code |
|---|---|
| Valid details, user created | 201 Created |
| Invalid input | 400 Bad Request or 422 Unprocessable Content, depending on your API's conventions |
| Email already registered | 409 Conflict |
| Unexpected database or application failure | 500 Internal Server Error |
app.post("/users", async (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: "Email and password are required" });
}
try {
const existing = await db.findUserByEmail(email);
if (existing) {
return res.status(409).json({ error: "Email already registered" });
}
// Real apps hash the password before saving it
const user = await db.createUser({ email, password });
return res.status(201).json({ id: user.id, email: user.email });
} catch (err) {
console.error(err);
return res.status(500).json({ error: "Something went wrong" });
}
});Key insight: The status code should describe what actually happened, not simply whether your code executed.
๐งช Try it yourself
-
Ask a test service to return any status code you like. The status line is the first line of the output:
bashcurl -i https://httpbin.org/status/404 curl -i https://httpbin.org/status/201 -
See a redirect. GitHub sends plain
http://requests to HTTPS, andLocationshows where:bashcurl -I http://github.com -
Open DevTools, go to the Network tab, and reload a page. Look at the Status column. You may spot
304for files your browser already had cached.
๐ฏ Day 8 in one line
2xx means success, 3xx means redirection, 4xx means a client-side request problem, and 5xx means a server-side or upstream failure.
Chapter 9 ยท Oct 29, 2026
Day 9: HTTP/1.1 vs HTTP/2 vs HTTP/3, HTTPS and TLS
Key takeaway
HTTP/1.1 reuses TCP connections, HTTP/2 multiplexes streams over one TCP connection, HTTP/3 uses QUIC over UDP, and HTTPS uses TLS to encrypt traffic in transit and authenticate the server. HTTPS does not replace auth or validation.
Your browser loads a website with dozens of images, scripts, and API calls.
How does HTTP handle all those requests? And what makes HTTPS secure?
Let's break it down.
1. First, What Is HTTP? ๐
HTTP (Hypertext Transfer Protocol) is the protocol clients and servers use to exchange requests and responses.
Your frontend sends a request. The server processes it and returns a response.
But websites became more complex. One page could require dozens of resources, and performance became increasingly important.
That's why HTTP evolved.
| Version | Standardized | Runs on |
|---|---|---|
| HTTP/1.1 | 1997 | TCP |
| HTTP/2 | 2015 | TCP |
| HTTP/3 | 2022 | QUIC over UDP |
The meaning of a request like GET /products stays the same in every version. What changes is how the messages travel.
In short: HTTP defines how clients and servers communicate. Newer versions improve how that communication works.
2. HTTP/1.1: One Connection, Limited Concurrency ๐ถ
HTTP/1.1 introduced persistent connections, allowing multiple requests to reuse a TCP connection instead of opening a new one every time.
However, responses on a single connection generally need to be delivered in order. A slow response can delay later responses on that connection.
Think of a single checkout lane. If one customer takes a long time, everyone behind them waits.
Browsers often work around this by opening multiple connections to the same server (commonly around six per host).
Example: A product page requests an HTML document, several images, CSS, and JavaScript files. The browser manages these requests using a combination of persistent and multiple connections.
In short: HTTP/1.1 supports connection reuse, but handling many concurrent resources can be less efficient.
3. HTTP/2: More Efficient Communication ๐
HTTP/2 introduced multiplexing, allowing multiple request and response streams to share a single connection.
It also uses binary framing and compresses HTTP headers with HPACK.
| Feature | What it means |
|---|---|
| Multiplexing | Many requests and responses travel at the same time over one connection |
| Binary framing | Messages are split into small binary frames instead of plain text lines |
| HPACK | Repeated headers (like cookies and user agents) are compressed |
Instead of needing separate connections for many concurrent requests, a browser can send multiple streams over one TCP connection.
Example: A page requests 20 product images. HTTP/2 can carry those requests concurrently over one connection.
The remaining problem: TCP head-of-line blocking
One limitation remains: HTTP/2 typically runs over TCP. If a TCP packet is lost, delivery of later data on that connection can be delayed while the missing data is recovered.
TCP delivers bytes strictly in order. So even if the lost packet belonged to image 3, data for images 4 to 20 can be held back until it is resent. All streams share that one ordered pipe.
In short: HTTP/2 improves concurrency and reduces overhead, but it still inherits TCP-level head-of-line blocking.
4. HTTP/3: Built on QUIC โก
HTTP/3 changes the underlying transport.
Instead of TCP, it uses QUIC, which runs over UDP and provides reliable streams, encryption, and connection management.
HTTP/3 allows independent streams to make progress without one lost packet blocking all the others at the transport layer.
It can also improve connection establishment and connection migration when network conditions change.
Example: You're using a website on your phone and switch from Wi-Fi to mobile data. QUIC can support connection migration, provided the connection and network conditions permit it.
In short: HTTP/3 uses QUIC to improve transport behavior, particularly for multiplexing and changing network conditions.
5. HTTP/1.1 vs HTTP/2 vs HTTP/3 ๐
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| Transport | TCP | TCP | QUIC over UDP |
| Connections | Persistent connections | One connection, many streams | One connection, many streams |
| Concurrency | Commonly uses multiple connections | Multiplexed streams | Multiplexed streams |
| Head-of-line blocking | Responses wait in order on a connection | Still affected at the TCP level | No TCP-level blocking between streams |
| Header compression | None | HPACK | QPACK |
| Message format | Text | Binary frames | Binary frames |
Here is how the layers stack up:
Important: HTTP/3 is not automatically faster in every situation. Actual performance depends on the network, server, client, and workload.
6. HTTP vs HTTPS ๐
HTTP defines how messages are exchanged. HTTPS is HTTP protected by TLS.
With ordinary HTTP, traffic is not encrypted by the protocol. It is like sending a postcard: anyone handling it along the way can read it.
With HTTPS, TLS provides:
| Protection | What it does |
|---|---|
| Confidentiality | Prevents network observers from reading protected traffic |
| Integrity | Helps detect unauthorized modification of traffic |
| Authentication | Allows the client to verify the server's identity using its certificate and the associated trust chain |
Example:
http://example.com -> The HTTP connection is not protected by TLS (default port 80)
https://example.com -> The connection uses TLS to protect HTTP traffic (default port 443)In short: HTTPS protects HTTP communication against eavesdropping and tampering and helps authenticate the server.
7. What Is TLS? ๐ค
TLS (Transport Layer Security) is the security protocol used by HTTPS.
At a high level:
- The client connects to the server.
- They negotiate security parameters and establish shared cryptographic keys.
- The client verifies the server's certificate.
- HTTP messages are exchanged over the protected connection.
Who handles TLS?
You generally don't need to implement TLS yourself. Your web server, reverse proxy, hosting platform, or load balancer usually handles it.
When a proxy decrypts the traffic before passing it to your app, we say it terminates TLS:
Browser --HTTPS--> Load balancer / reverse proxy --HTTP--> Your Node.js app
(TLS terminates here)In this setup, your app may see a plain http request even though the user connected over HTTPS. In Express, you tell the app to trust the proxy's forwarded information:
// Trust the first proxy in front of the app,
// so req.protocol and req.secure reflect the original HTTPS request
app.set("trust proxy", 1);In short: TLS creates a secure channel through which HTTP messages can travel.
8. What Should a Backend Developer Know? ๐งฐ
When building and deploying APIs:
- Use HTTPS for production traffic.
- Understand whether your server or reverse proxy terminates TLS.
- Know which HTTP versions your infrastructure supports.
- Don't assume every client supports HTTP/3.
- Remember that HTTPS encrypts traffic in transit, not data automatically stored in your database.
- Never treat HTTPS as a replacement for authentication, authorization, or input validation.
Key insight: HTTPS protects the road, not the destination. Once data reaches your server, your own code is responsible for checking and protecting it.
Common mistakes
- Thinking HTTPS tells you who the user is. TLS lets the client verify the server. You still need login and permissions.
- Loading
http://scripts or images on anhttps://page (mixed content). Browsers block or warn about this. - "Fixing" certificate errors by turning off verification in code, for example with
NODE_TLS_REJECT_UNAUTHORIZED=0. This removes the protection HTTPS gives you.
๐งช Try it yourself
-
Ask for HTTP/2 and check what the server agrees to. The first line shows the version used:
bashcurl -I --http2 https://example.comIf the server supports it, you'll see
HTTP/2 200. -
Watch the TLS handshake. The lines starting with
*describe the TLS version and the server's certificate:bashcurl -v https://example.com -
In your browser, open DevTools, go to the Network tab, right-click a column header and enable Protocol. Reload the page and you'll see values like
http/1.1,h2orh3.
๐ฏ Day 9 in one line
HTTP/1.1 uses persistent connections, HTTP/2 adds multiplexing over TCP, HTTP/3 uses QUIC over UDP, and HTTPS uses TLS to secure HTTP communication.
Chapter 10 ยท Oct 30, 2026
Day 10: Cookies, Sessions and CORS
Key takeaway
Cookies store small data in the browser, sessions keep state on the server behind a session ID cookie, and CORS tells browsers which origins may read responses. CORS is not authentication, and with credentials the allowed origin cannot be *.
You log in to a website, close the browser, and open it again later. Sometimes you're still logged in.
How does the server remember you between requests?
And why does your browser sometimes block an API request even when the backend is working perfectly?
Let's understand three important concepts.
1. The Problem: HTTP Is Stateless ๐ง
HTTP is a stateless protocol.
This means the server doesn't automatically remember previous requests.
Imagine:
- You log in to an application.
- The server verifies your credentials.
- You request your profile.
- The server receives another request.
How does it know that the second request belongs to the user who just logged in?
HTTP doesn't automatically maintain that relationship. We need a mechanism to maintain state across requests.
In short: HTTP treats each request independently. Cookies and sessions help applications maintain state.
2. Cookies: Small Data Stored in the Browser ๐ช
A cookie is a small piece of data that a server asks the browser to store.
The server sends a Set-Cookie response header:
HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=LaxThe browser stores the cookie and automatically sends it with matching future requests:
- Server sends a cookie.
- Browser stores it.
- Browser makes another request to the matching domain and path.
- Browser includes the cookie in the request's
Cookieheader.
GET /profile HTTP/1.1
Host: example.com
Cookie: sessionId=abc123Important cookie attributes
| Attribute | What it does |
|---|---|
HttpOnly | Prevents JavaScript from reading the cookie (it won't appear in document.cookie) |
Secure | Sends the cookie only over secure (HTTPS) connections |
SameSite | Controls when cookies are sent with cross-site requests: Strict, Lax or None |
Max-Age / Expires | Controls cookie lifetime. Without them, the cookie usually disappears when the browser session ends |
That last row explains the opening question: a cookie with a long Max-Age survives closing the browser, so you stay logged in.
In Express, setting a cookie looks like this:
res.cookie("sessionId", "abc123", {
httpOnly: true,
secure: true,
sameSite: "lax",
maxAge: 24 * 60 * 60 * 1000, // 1 day, in milliseconds
});In short: Cookies allow browsers to store and automatically send small pieces of data with requests.
3. Sessions: How the Server Remembers You ๐๏ธ
A session is a way for the server to maintain information about a user's interaction across multiple requests.
A common session-based approach works like this:
Example:
Browser cookie:
sessionId=abc123Server-side session data:
{
"userId": "user42",
"role": "admin",
"loggedIn": true
}The browser holds the session ID. The actual session data is maintained on the server.
The session store could be memory, Redis or a database. Memory is fine for learning, but it is wiped when the server restarts and isn't shared between multiple servers.
In short: The cookie carries the session ID. The server uses that ID to find the user's session.
4. Cookies vs Sessions ๐จ
These are related, but they aren't the same thing.
| Cookies | Sessions | |
|---|---|---|
| Lives in | The browser | The server (memory, Redis or a database) |
| How it travels | Automatically sent with matching requests | Commonly identified using a session ID, usually in a cookie |
| What it holds | Different kinds of small data | User-related information |
| Control | Attributes control their behavior | Can be invalidated on the server |
Think of a hotel: the cookie is your room key card. The session is your actual booking information stored in the hotel's system.
Because the session lives on the server, logging out can truly end it. The server deletes the session, and the old session ID becomes useless.
In short: Cookies are a browser storage mechanism. Sessions are a way to maintain server-side state.
5. CORS: Why Is the Browser Blocking My API? ๐ง
Imagine:
- Your frontend runs at
http://localhost:3000 - Your backend runs at
http://localhost:5000 - You try to call
http://localhost:5000/api/users
The browser may block your frontend JavaScript from accessing the response.
Why? Because these are different origins.
An origin consists of:
- Scheme:
httporhttps - Host:
localhost - Port:
3000or5000
Even though both applications run on localhost, their ports are different, so their origins are different.
| URL | Same origin as http://localhost:3000? |
|---|---|
http://localhost:3000/dashboard | Yes, only the path differs |
http://localhost:5000/api/users | No, the port differs |
https://localhost:3000 | No, the scheme differs |
http://127.0.0.1:3000 | No, the host differs |
This is where CORS comes in. CORS stands for Cross-Origin Resource Sharing.
It is a browser-enforced mechanism that allows servers to specify which origins can access their responses.
This is the error you'll typically see in the browser console:
Access to fetch at 'http://localhost:5000/api/users' from origin
'http://localhost:3000' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.In short: CORS controls which cross-origin responses browser JavaScript is allowed to access.
6. How CORS Works โ๏ธ
Your frontend sends a request to another origin. The backend can respond with a header such as:
Access-Control-Allow-Origin: http://localhost:3000This tells the browser that the specified origin is allowed to access the response.
If the origin is not allowed, the browser prevents JavaScript from accessing the response.
One important detail: preflight requests
For certain cross-origin requests, the browser first sends an OPTIONS request. This is called a preflight request. It asks the server whether the actual request is permitted.
OPTIONS /api/users
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, AuthorizationThe server responds with the appropriate CORS headers:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, AuthorizationIf the preflight succeeds, the browser proceeds with the actual request.
Not every cross-origin request requires a preflight:
| Request | Preflight? |
|---|---|
Simple GET with no custom headers | Usually no |
POST with Content-Type: application/json | Yes |
Any request with an Authorization header | Yes |
PUT, PATCH or DELETE | Yes |
In short: CORS uses HTTP headers, and sometimes an
OPTIONSpreflight, to control cross-origin browser access.
7. CORS Is Not Authentication ๐
Suppose your backend allows:
Access-Control-Allow-Origin: *Does this mean everyone is authorized to access your API? No.
CORS is not an authentication or authorization mechanism. It primarily controls whether browser JavaScript from another origin can read a response.
A request made through Postman, curl or another backend server isn't restricted by browser CORS enforcement:
# No browser, no CORS check. This works whatever your CORS settings are.
curl -i http://localhost:5000/api/usersYour backend must still authenticate users, authorize operations and protect sensitive resources.
In short: CORS controls browser access to responses. It does not secure your API by itself.
8. Cookies and CORS Together ๐ค
Now imagine your frontend and backend run on different origins, and you want to use session cookies.
Three things matter:
- The browser must be configured to include credentials in the cross-origin request.
- The backend must allow the specific frontend origin and credentials.
- The cookie's
SameSite,Secureand other attributes must permit it to be sent.
For example, with fetch:
fetch("https://api.example.com/profile", {
credentials: "include",
});And the backend needs appropriate CORS response headers, including:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: trueIn Express, the cors package sets these headers and answers preflights for you:
import cors from "cors";
app.use(cors({
origin: "https://app.example.com", // a specific origin, never "*"
credentials: true,
}));Key insight: When credentials are allowed, the server cannot use * as the allowed origin.
One more nuance: app.example.com and api.example.com are different origins but the same site. If your frontend and API are on completely different sites (say myapp.com and myapi.net), the cookie usually needs SameSite=None; Secure, and some browsers restrict these third-party cookies further.
In short: Cross-origin cookie-based sessions require coordination between browser settings, cookie attributes and backend CORS configuration.
Common mistakes
- Using
Access-Control-Allow-Origin: *together with credentials. The browser rejects it. - Forgetting
credentials: "include"infetch, so cross-origin cookies are never sent or stored. - Trying to "fix" CORS in frontend code. CORS is configured on the server.
- Leaving
HttpOnlyoff session cookies, so any injected script can read them. - Using an in-memory session store in production with multiple servers.
๐งช Try it yourself
-
Open DevTools on a site you're logged in to. In Chrome, go to Application โ Cookies (in Firefox, Storage โ Cookies). Check the
HttpOnly,SecureandSameSitecolumns. -
In the Console of the same site, type
document.cookie. Cookies markedHttpOnlywon't appear in the result. -
Run a local API and pretend to be a browser sending a preflight:
bashcurl -i -X OPTIONS http://localhost:5000/api/users \ -H "Origin: http://localhost:3000" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: Content-Type"Look for the
Access-Control-Allow-*headers. Then try anOriginyour server doesn't allow.
๐ฏ Day 10 in one line
Cookies store data in the browser, sessions maintain state on the server, and CORS controls which cross-origin responses browser JavaScript can access.
Chapter 11 ยท Oct 31, 2026
Day 11: Building an HTTP Server from Scratch, Then Express
Key takeaway
Node's built-in http module creates the server and hands you req and res for every request. Express is a framework built on top of it that adds routing and response helpers, and app.listen() still starts a Node HTTP server underneath.
Ever wondered what actually happens when you write app.get("/users", ...) in Express?
What if I told you that Node.js can create an HTTP server without Express?
Let's build the understanding from scratch.
1. What Is an HTTP Server? ๐ฅ๏ธ
An HTTP server is a program that listens for incoming HTTP requests and sends back HTTP responses.
In Node.js, you don't need Express to create one. Node provides a built-in module called http. It gives you the basic tools to:
- Create an HTTP server
- Receive incoming requests
- Read request information
- Send HTTP responses
- Listen on a specific port
Because http is built into Node.js, there is nothing to install.
In short: Express is not required to create an HTTP server. Node.js already provides the underlying functionality.
2. Your First Node.js Server ๐
Let's create a basic HTTP server. Save this as server.js:
const http = require("http");
const server = http.createServer((req, res) => {
res.end("Hello from Node.js!");
});
server.listen(3000, () => {
console.log("Server running on port 3000");
});Run it with node server.js, then visit http://localhost:3000. You'll see:
Hello from Node.js!What happened?
| Piece | What it does |
|---|---|
http.createServer() | Creates an HTTP server |
The callback (req, res) => { ... } | Runs every time a request reaches the server |
req | Represents the incoming request |
res | Represents the response being constructed |
server.listen(3000) | Starts listening on port 3000 |
Key insight: You never call the callback yourself. Node calls it for you, once per request, and hands you fresh req and res objects each time.
In short: Node creates the server, receives requests, and gives you the objects needed to construct responses.
3. Understanding req and res ๐จ
These two objects are fundamental to backend development.
req (IncomingMessage)
Contains information about the incoming request.
| Property | What it holds | Example |
|---|---|---|
req.method | HTTP method | GET |
req.url | Requested URL (path and query string) | /users?page=2 |
req.headers | Request headers | { host: "localhost:3000", ... } |
const server = http.createServer((req, res) => {
console.log(req.method);
console.log(req.url);
console.log(req.headers);
// Always finish the response, or the browser keeps waiting
res.end("Check your terminal");
});If you visit GET /users, you can inspect the method, URL and headers received by your server in the terminal.
res (ServerResponse)
Used to construct and send the HTTP response.
| Method or property | What it does |
|---|---|
res.statusCode | Set the status code |
res.setHeader() | Set response headers |
res.write() | Write response data (can be called many times) |
res.end() | Finish the response |
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader(
"Content-Type",
"application/json"
);
res.end(JSON.stringify({
message: "Success"
}));
});Notice that with the raw http module, you have to set the Content-Type header and turn the object into a JSON string yourself.
In short:
reqgives you the incoming request.reslets you construct and send the response.
4. Routing Without Express ๐ฃ๏ธ
Suppose you want two endpoints:
GET /usersGET /products
Using Node's HTTP module, you have to check the method and URL yourself.
const server = http.createServer((req, res) => {
if (req.method === "GET" && req.url === "/users") {
res.end("Users list");
}
else if (req.method === "GET" && req.url === "/products") {
res.end("Products list");
}
else {
res.statusCode = 404;
res.end("Route not found");
}
});It works! But imagine handling 50 endpoints, query parameters, request bodies, and different response formats.
| You would have to handle yourself | Why it gets painful |
|---|---|
| Matching method and URL | A huge if/else chain |
| Query strings | req.url is /users?page=2, so === "/users" fails |
| Route parameters | Extracting 42 from /users/42 by hand |
| Request bodies | The body arrives as a stream you must collect |
| JSON responses | Headers and JSON.stringify every time |
Your server code starts becoming difficult to manage.
In short: Node's HTTP module gives you control, but you have to handle much of the request processing yourself.
5. So, What Is Express? โก
Express is a web framework built on top of Node.js HTTP functionality. It provides a higher-level interface for building web servers and APIs.
Instead of manually checking every method and URL, you can write (after npm install express):
const express = require("express");
const app = express();
app.get("/users", (req, res) => {
res.send("Users list");
});
app.get("/products", (req, res) => {
res.send("Products list");
});
app.listen(3000);Notice the difference?
app.get()handles GET requests for a particular path.reqandresare still available.app.listen()starts the server.- Unknown paths get a
404reply (Cannot GET /orders) without any extra code.
Express handles much of the repetitive HTTP server work for you.
In short: Express provides a convenient abstraction over Node.js HTTP functionality.
6. Node HTTP vs Express โ๏ธ
Node.js http module | Express | |
|---|---|---|
| Where it comes from | Built into Node.js | External framework |
| Level | Low-level control over HTTP handling | Convenient, higher-level APIs |
| Routing | Manual if/else on method and URL | app.get(), app.post() and friends |
| Responses | Status, headers and JSON.stringify by hand | res.status(), res.send(), res.json() |
| Amount of code | Lots of repetition | Much less repetitive code |
| Best for | Understanding how servers work | Building real APIs quickly |
Think of it this way: Node.js HTTP is the foundation. Express is a framework that makes building on that foundation easier.
7. Does Express Replace Node HTTP? ๐
No! Express still uses Node.js HTTP functionality underneath.
When you call app.listen(3000), Express ultimately creates and starts an HTTP server using Node.js. These two lines do the same job:
app.listen(3000);
// is roughly a shortcut for:
const http = require("http");
http.createServer(app).listen(3000);You are not replacing Node's HTTP server. You are using a framework built around it.
Key insight: The req and res you get in Express are Node's own request and response objects, with extra helpers like res.json() and res.status() added on top.
8. What Breaks Without This? ๐ฅ
Imagine you start learning Express directly. You write:
app.get("/users", (req, res) => {
res.json({ users: [] });
});But you don't understand the questions below. Understanding Node's HTTP module answers them:
| Question | Answer |
|---|---|
Where does req come from? | Node builds it from the incoming HTTP request |
Who creates res? | Node, a fresh one for every request |
| How does the server know which callback to run? | Express matches the method and path to a route |
| What happens when a request reaches port 3000? | Node's HTTP server, listening on that port, receives it |
| How does the response reach the browser? | res writes it back over the same connection |
You don't need to build every production application using the low-level HTTP module. But understanding the foundation makes debugging and learning frameworks much easier.
Common mistakes
- Forgetting
res.end()in a raw Node server, so the browser keeps loading forever. - Passing an object straight to
res.end(). It only accepts strings or Buffers, so useJSON.stringify(). - Sending JSON without a
Content-Type: application/jsonheader. - Starting a second server on a port that is already in use, which fails with
EADDRINUSE.
๐งช Try it yourself
- Save the routing example from section 4 (with
const http = require("http");at the top andserver.listen(3000);at the bottom) and runnode server.js. - In a second terminal, run
curl -i http://localhost:3000/users, thencurl -i http://localhost:3000/orders. Compare the200 OKand404 Not Foundstatus lines. - Now try
curl -i "http://localhost:3000/users?page=2". You get a 404, becausereq.urlincludes the query string. This is exactly the kind of work Express does for you.
๐ฏ Day 11 in one line
Node.js provides the HTTP server functionality. Express builds a convenient abstraction on top of it, making backend development easier.
Phase 2 complete! ๐ฏ We've covered HTTP methods, URLs, parameters, headers, request bodies, status codes, HTTP versions, TLS, cookies, sessions and CORS.
Chapter 12 ยท Nov 1, 2026
Day 12: Setting Up Node.js and Express (Your First Backend Server)
Key takeaway
Node.js runs your code, npm manages dependencies through package.json, and Express builds the server. app.listen(3000) makes it reachable at http://localhost:3000, and npm scripts plus Nodemon make running it easy.
We've spent 11 days understanding how the backend works. Now it's time to build one.
Let's create our first Node.js + Express server from scratch.
1. What Do We Need? ๐งฐ
Before writing code, let's understand our tools.
| Tool | What it is | Its job |
|---|---|---|
| Node.js | A JavaScript runtime | Executes JavaScript outside the browser |
| npm | A package manager (installed with Node.js) | Installs and manages project dependencies |
| Express | A web framework | Simplifies building HTTP servers and APIs |
Think of it this way: Node.js runs your backend code. npm manages the packages. Express helps you build the server.
Check that Node.js and npm are installed:
node -v
npm -vIf both print a version number, you're ready.
2. Create Your Project ๐
First, create a project directory:
mkdir my-backend
cd my-backendInitialize a Node.js project:
npm init -yThis creates a package.json file. The -y flag accepts all the default answers.
What is package.json?
It's the configuration file that describes your Node.js project. It contains:
- Project name and version
- Dependencies
- Development scripts
- Other project metadata
It will look roughly like this (the exact fields vary by npm version):
{
"name": "my-backend",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "ISC"
}In short:
npm initinitializes your project and creates its package configuration.
3. Install Express ๐ฆ
Now install Express:
npm install expressWhat happens here?
| What you get | What it is for |
|---|---|
| Express downloaded | npm fetches Express and the packages it depends on |
"dependencies" in package.json | Records that your project needs Express, e.g. "express": "^5.1.0" (your version may differ) |
node_modules/ folder | Holds the actual downloaded package code |
package-lock.json | Records the exact dependency versions installed |
You don't need to manually download or manage Express files.
Key insight: node_modules/ can always be rebuilt by running npm install, because package.json and package-lock.json describe exactly what belongs in it. That's why it is usually left out of Git.
In short: npm installs Express and manages the packages your application depends on.
4. Create Your First Express Server โ๏ธ
Create a file named server.js:
const express = require("express");
const app = express();
app.get("/", (req, res) => {
res.send("Backend server is running!");
});
app.listen(3000, () => {
console.log("Server running on port 3000");
});Let's understand what's happening.
| Line | What it does |
|---|---|
require("express") | Imports Express |
express() | Creates an Express application |
app.get("/", ...) | Defines a basic GET endpoint for the path / |
req | Represents the incoming request |
res | Used to send the response |
app.listen(3000, ...) | Starts the server on port 3000 and runs the callback once it's ready |
In short: You now have a working Express application listening for incoming HTTP requests.
5. Start Your Server โถ๏ธ
Run:
node server.jsYou should see:
Server running on port 3000Now open your browser at http://localhost:3000. You'll see:
Backend server is running!Congratulations! Your first Express server is running locally. ๐
The terminal stays busy because the server keeps listening for requests. Press Ctrl + C to stop it.
6. What Is localhost? ๐
When you visit http://localhost:3000, you're accessing a server running on your own machine.
| Part | Meaning |
|---|---|
http:// | The protocol your browser uses to talk to the server |
localhost | Your own machine (it points to the loopback address 127.0.0.1) |
3000 | The port your server is listening on |
Your browser sends an HTTP request to that port, Node.js receives it, Express handles the matching endpoint, and the response goes back to your browser.
In short: Localhost lets you test your backend on your own machine without deploying it to the internet.
7. Add npm Scripts โ๏ธ
Instead of typing node server.js every time, we can add a script to package.json:
{
"scripts": {
"start": "node server.js"
}
}Now run:
npm startWith plain node, you must stop and restart the server after every code change. You can install Nodemon for development:
npm install -D nodemonThe -D flag saves it under devDependencies, because it's only needed while developing. Add another script:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}Now:
npm run devNodemon automatically restarts your server when it detects file changes.
| Command | What it runs | When to use it |
|---|---|---|
npm start | node server.js | Running the app normally |
npm run dev | nodemon server.js | While you're writing code |
start is a special script name, so npm start works without run. Custom names like dev need npm run dev.
In short: npm scripts simplify running your application, while Nodemon improves the development workflow.
8. Your Project Structure ๐๏ธ
At this stage, your project should look like this:
my-backend/
โ
โโโ node_modules/
โโโ package.json
โโโ package-lock.json
โโโ server.jsThat's all you need to get your first Express server running. We'll introduce routes, controllers, services and a proper project structure on Day 13.
9. What Breaks Without This? ๐ฅ
Imagine you install Express, copy some code from a tutorial, and run it without understanding the setup. Your server doesn't start.
You don't know:
- Which file starts the application.
- Where dependencies are installed.
- What
app.listen()actually does. - Why your browser cannot connect to port 3000.
Understanding the setup makes debugging much easier.
| What you see | Likely cause | Fix |
|---|---|---|
Error: Cannot find module 'express' | Express isn't installed in this folder | Run npm install express (or npm install) |
Cannot find module '.../server.js' | Wrong folder or wrong filename | cd my-backend and check the name |
EADDRINUSE: address already in use :::3000 | Another server is already on port 3000 | Stop it with Ctrl + C, or use another port |
| Browser says it can't connect | The server isn't running or crashed | Check the terminal and start it again |
| Code changes don't show up | Plain node is still running old code | Restart, or use npm run dev |
Common mistakes
- Running npm commands outside the project folder, so
package.jsonisn't found. - Committing
node_modules/to Git instead of adding it to.gitignore. - Forgetting to save the file before testing.
- Listening on one port and visiting another in the browser.
You don't just want code that runs. You want to understand why it runs.
๐งช Try it yourself
- Start the server with
npm run dev, then in another terminal runcurl -i http://localhost:3000. Look forHTTP/1.1 200 OK, theX-Powered-By: Expressheader and your message. - Change the text in
res.send()and save. Watch Nodemon restart, then run thecurlcommand again. - Visit
http://localhost:3000/hello. Express replies with a404andCannot GET /hello, because no route matches that path yet.
๐ฏ Day 12 in one line
Node.js executes your backend code, npm manages dependencies, and Express provides the framework to build your HTTP server.
Chapter 13 ยท Nov 2, 2026
Day 13: Routing and Project Structure (Routes, Controllers, Services)
Key takeaway
Routes decide where a request goes, controllers handle req and res, and services hold reusable business logic that knows nothing about HTTP. The flow is Request, Route, Controller, Service, Controller, Response.
Imagine building an e-commerce backend with 50 API endpoints. Would you put all 50 endpoints inside server.js?
It might work initially, but maintaining it would quickly become difficult. Let's understand how backend developers organize their applications.
1. The One-File Problem ๐
Imagine your server.js contains:
- User registration and login
- Product listing and searching
- Cart management
- Order creation
- Payment processing
Every endpoint, database operation and business rule lives in the same file. As the application grows:
- Finding code becomes difficult.
- Changing one feature becomes risky.
- Reusing business logic becomes harder.
- Testing individual parts becomes inconvenient.
We need a better structure.
In short: A small application can start with one file, but growing applications need separation of responsibilities.
2. What Is Routing? ๐ฃ๏ธ
A route determines which code should handle an incoming HTTP request. A route is generally identified by:
- HTTP method
- URL path
// getUsers, createUser and friends are handler functions defined elsewhere
app.get("/users", getUsers);
app.post("/users", createUser);
app.get("/products", getProducts);
app.delete("/products/:id", deleteProduct);| Method | Path | What it does |
|---|---|---|
GET | /users | Retrieves users |
POST | /users | Creates a user |
GET | /products | Retrieves products |
DELETE | /products/:id | Deletes a product |
The :id part is a route parameter. For DELETE /products/42, Express gives you req.params.id === "42".
Express matches the incoming request to the appropriate route handler.
In short: Routing connects incoming HTTP requests to the code responsible for handling them.
3. Express Router ๐งญ
Express provides express.Router() to create separate route modules. Instead of defining every endpoint inside server.js, we can organize routes by feature.
// routes/user.routes.js
const express = require("express");
const router = express.Router();
router.get("/", (req, res) => {
res.send("Get all users");
});
router.post("/", (req, res) => {
res.send("Create user");
});
module.exports = router;Now connect it to the main application:
// server.js
const express = require("express");
const userRoutes = require("./routes/user.routes");
const app = express();
app.use("/users", userRoutes);
app.listen(3000);Now:
GET /usersreaches the GET route.POST /usersreaches the POST route.
Notice that we don't need to repeat /users inside every route. The mount path and the router path are joined together:
Mount path in app.use() | Path in the router | Full URL |
|---|---|---|
/users | / | /users |
/users | /:id | /users/42 |
In short: Express Router lets us group related endpoints into separate modules.
4. Controllers ๐ฎ
A controller handles the incoming request and prepares the response. Instead of writing the request-handling logic directly inside the route file, we move it into a controller.
// controllers/user.controller.js
const getUsers = (req, res) => {
res.json({
users: ["Dev", "Sahil"]
});
};
const createUser = (req, res) => {
res.status(201).json({
message: "User created"
});
};
module.exports = {
getUsers,
createUser
};Now our route file becomes:
// routes/user.routes.js
const express = require("express");
const router = express.Router();
const {
getUsers,
createUser
} = require("../controllers/user.controller");
router.get("/", getUsers);
router.post("/", createUser);
module.exports = router;The route decides which controller function should execute. The controller handles the request and sends the response.
In short: Routes define where requests go. Controllers handle the incoming requests.
5. Services ๐ง
Now imagine creating a user requires:
- Checking whether the email already exists.
- Processing the user's information.
- Creating the user record.
- Returning the result.
Should all this business logic live inside the controller? Not necessarily. We can move the core business operations into a service.
// services/user.service.js
const createUser = async (userData) => {
// Business logic goes here
return {
id: "user123",
name: userData.name,
email: userData.email
};
};
module.exports = {
createUser
};The controller calls the service. Here is the full controller, now using it:
// controllers/user.controller.js
const userService = require("../services/user.service");
const getUsers = (req, res) => {
res.json({ users: ["Dev", "Sahil"] });
};
const createUser = async (req, res) => {
const user = await userService.createUser(req.body);
res.status(201).json(user);
};
module.exports = { getUsers, createUser };The service focuses on the application's business operations rather than handling HTTP requests and responses.
| Layer | Knows about req and res? | Its job |
|---|---|---|
| Route | Yes | Maps a method and path to a controller |
| Controller | Yes | Reads the request, calls the service, sends the response |
| Service | No | Runs the business logic and returns data |
Key insight: Because a service never touches req or res, the same function can be reused by several controllers, a background job or a test.
In short: Services contain reusable business logic. Controllers coordinate the request and response.
6. The Complete Request Flow ๐
Let's say a client sends POST /users. Here's what happens:
- Server: Receives the incoming HTTP request.
- Router: Matches
POST /users. - Controller: Handles the request and extracts the required information.
- Service: Executes the business operation.
- Controller: Receives the result and prepares the response.
- Client: Receives the HTTP response.
Think of it as:
Request โ Route โ Controller โ Service โ Controller โ ResponseThis is a common architecture, not a mandatory rule for every Express application.
7. Project Structure ๐๏ธ
A simple example:
my-backend/
โ
โโโ controllers/
โ โโโ user.controller.js
โ
โโโ routes/
โ โโโ user.routes.js
โ
โโโ services/
โ โโโ user.service.js
โ
โโโ server.js
โโโ package.json
โโโ package-lock.jsonEach folder has a responsibility:
| File or folder | Responsibility |
|---|---|
routes/ | Defines API endpoints |
controllers/ | Handles requests and responses |
services/ | Contains business logic |
server.js | Creates and starts the application |
Since createUser reads req.body, server.js needs one extra line so Express can read JSON bodies (more on this on Day 15):
// server.js
const express = require("express");
const userRoutes = require("./routes/user.routes");
const app = express();
app.use(express.json()); // fills req.body from JSON requests
app.use("/users", userRoutes);
app.listen(3000, () => {
console.log("Server running on port 3000");
});As your application grows, you can organize additional features using the same pattern, for example product.routes.js, product.controller.js and product.service.js.
8. Folders Are Not Architecture โ ๏ธ
Creating folders doesn't automatically mean you have good architecture.
Common mistakes
- Putting database queries and business rules everywhere.
- Writing business logic directly inside route handlers.
- Making controllers unnecessarily large.
- Creating too many layers for a very small application.
The goal isn't to create more files. The goal is to keep responsibilities clear and make the code easier to maintain.
9. What Breaks Without Separation? ๐ฅ
Imagine your payment calculation logic is written directly inside five different controllers. Now the discount calculation changes.
You have to update five places. What if you miss one? Different parts of your application may calculate different prices.
If the calculation lives in a reusable service, multiple controllers can use the same business logic:
Change the discount rule once in the service, and every controller gets the update.
๐งช Try it yourself
- Create the route file from section 4, the service and full controller from section 5, and the
server.jsfrom section 7. Then runnode server.js. - Run
curl -i http://localhost:3000/users. You should get200 OKand{"users":["Dev","Sahil"]}. - Create a user:
curl -i -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Dev","email":"dev@example.com"}'You should get 201 Created and the user object returned by the service.
๐ฏ Day 13 in one line
Routes decide where requests go, controllers handle HTTP requests and responses, and services contain business logic.
Chapter 14 ยท Nov 3, 2026
Day 14: Express Middleware (The Chain, next() and Writing Your Own)
Key takeaway
Express runs middleware in the order you register it, and every middleware must either call next() to pass control forward or send a response itself. If it does neither, the request hangs.
Ever wondered how logging, request processing and other operations happen before your API controller executes?
That's where middleware comes in. Let's understand how Express processes a request internally.
1. What Is Middleware? ๐งฉ
Middleware is a function that executes during the request-response cycle. It gets access to three things:
| Parameter | What it is |
|---|---|
req | The incoming request |
res | The response object |
next | A function used to pass control to the next middleware |
A middleware can:
- Execute some code.
- Read or modify the request.
- Modify the response.
- End the request-response cycle.
- Pass control to the next middleware.
Think of airport security checkpoints. Each checkpoint inspects you and then either waves you on to the next one or stops you right there.
const myMiddleware = (req, res, next) => {
// 1. Do some work
// 2. Then call next() OR send a response
next();
};In short: Middleware sits between receiving a request and sending the final response.
2. Why Do We Need Middleware? ๐ค
Imagine an e-commerce backend with 50 API endpoints. Before processing requests, you might want to:
- Log incoming requests.
- Check request information.
- Measure processing time.
- Apply common processing logic.
Would you write the same code inside all 50 endpoints? That creates duplication and makes maintenance difficult.
Instead, middleware lets you write common functionality once and reuse it across multiple routes.
In short: Middleware helps separate reusable request-processing logic from individual route handlers.
3. How the Middleware Chain Works โ๏ธ
Imagine a request arriving at your Express server. It passes through a sequence of middleware functions before reaching the route handler.
const express = require("express");
const app = express();
app.use((req, res, next) => {
console.log("Middleware 1");
next();
});
app.use((req, res, next) => {
console.log("Middleware 2");
next();
});
app.get("/", (req, res) => {
res.send("Hello from backend!");
});
app.listen(3000);When you visit /, the output is:
Middleware 1
Middleware 2Then the route handler sends the response. The execution flow:
Request โ Middleware 1 โ Middleware 2 โ Route Handler โ ResponseExpress processes middleware in the order in which it is registered.
In short: Express executes matching middleware sequentially. Order matters.
4. What Exactly Does next() Do? โก๏ธ
This is where beginners often get confused.
next() tells Express to pass control to the next matching middleware or handler in the processing chain.
app.use((req, res, next) => {
console.log("First");
next();
});
app.use((req, res, next) => {
console.log("Second");
next();
});
app.get("/", (req, res) => {
console.log("Third");
res.send("Done");
});Output:
First
Second
ThirdBut what happens if you remove next()?
app.use((req, res, next) => {
console.log("First");
});The request gets stuck because this middleware neither passes control forward nor sends a response.
Important: A middleware must either pass control forward or complete the response. Otherwise, the request can hang.
A middleware is allowed to end the chain itself. For example:
const MAINTENANCE_MODE = true;
app.use((req, res, next) => {
if (MAINTENANCE_MODE) {
return res.status(503).send("Down for maintenance");
}
next();
});The return matters here: it stops the function so next() is not also called after the response is sent.
In short:
next()moves processing forward. It does not itself send a response.
5. Middleware Can Modify the Request โ๏ธ
Middleware doesn't just execute code. It can also attach information to the request object.
app.use((req, res, next) => {
req.requestTime = new Date();
next();
});
app.get("/", (req, res) => {
res.json({
time: req.requestTime
});
});Here:
- Middleware receives the request.
- It adds
requestTimetoreq. - It calls
next(). - The route handler accesses the added property.
This is useful when information needs to be shared between middleware and route handlers.
In short: Middleware can enrich the request before it reaches the route handler.
6. Application-Level vs Route-Level ๐ฏ
Middleware doesn't always need to run for every endpoint.
Application-level middleware
Registered using app.use():
app.use((req, res, next) => {
console.log("Request received");
next();
});This runs for matching requests across the application.
Route-level middleware
Applied only to specific routes:
const checkRequest = (req, res, next) => {
console.log("Checking this route");
next();
};
app.get("/profile", checkRequest, (req, res) => {
res.send("Profile");
});
app.get("/products", (req, res) => {
res.send("Products");
});Here, checkRequest runs for /profile, but not /products.
| Application-level | Route-level | |
|---|---|---|
| Registered with | app.use(fn) | app.get(path, fn, handler) |
| Runs for | Matching requests across the app | Only the routes you attach it to |
| Typical use | Logging, reading request bodies | Checks needed by one or a few routes |
In short: Application-level middleware applies broadly. Route-level middleware applies to selected routes.
7. Writing Your Own: A Request Logger ๐ ๏ธ
Let's create a simple request logger.
const requestLogger = (req, res, next) => {
const start = Date.now();
console.log(
`${req.method} ${req.url}`
);
res.on("finish", () => {
console.log(
`Completed in ${Date.now() - start}ms`
);
});
next();
};
app.use(requestLogger);Now every matching request logs:
GET /users
Completed in 12ms
POST /products
Completed in 8msNotice something interesting: the middleware starts before the route handler, but the finish event runs when the response has been sent. This lets us measure the complete response time.
In short: You can create custom middleware to implement reusable functionality specific to your application.
8. What Breaks Without This? ๐ฅ
Imagine you write:
app.use((req, res, next) => {
console.log("Request received");
});
app.get("/users", (req, res) => {
res.send("Users");
});You open /users, but your browser keeps loading. Why?
Because the middleware never calls next() and never sends a response. The route handler is never reached.
This is one of the most common middleware mistakes.
Common mistakes
- Forgetting to call
next(), so the request hangs. - Sending a response and then also calling
next(). A later handler tries to respond again and you getCannot set headers after they are sent to the client. - Registering middleware after a route. If that route already sent the response, the middleware never runs for it.
- Writing
checkRequest()instead ofcheckRequestin a route. The parentheses call the function immediately instead of handing it to Express.
9. The Complete Picture ๐ผ๏ธ
| Piece | Responsibility |
|---|---|
| Request | Carries the incoming HTTP request |
| Middleware | Executes reusable processing logic |
next() | Passes control forward |
| Route handler | Handles the matched endpoint and typically sends the response |
A typical flow looks like:
Client โ Middleware 1 โ Middleware 2 โ Route Handler โ ResponseThe chain can also end inside a middleware if it sends the response itself.
๐งช Try it yourself
- Run the server from section 3 and visit
http://localhost:3000. Check your terminal forMiddleware 1andMiddleware 2(you may see them twice, because browsers also request/favicon.ico). - Delete
next();from Middleware 1, restart, and runcurl -i --max-time 5 http://localhost:3000/. Nothing comes back, and curl gives up after 5 seconds with a timeout error. - Put
next()back, then register therequestLoggerfrom section 7 withapp.use()before the other middleware, and hit a few URLs. Watch each request log its method, URL and timing.
๐ฏ Day 14 in one line
Middleware functions process requests in sequence.
next()passes control forward, and custom middleware lets you reuse logic across your Express application.
Chapter 15 ยท Nov 4, 2026
Day 15: Built-in and Third-Party Middleware (JSON, Morgan, CORS, Helmet, Rate Limiting)
Key takeaway
Use express.json() and trusted packages like morgan, cors, helmet and express-rate-limit instead of reinventing them, and register them before your routes in a deliberate order. CORS is not authentication, and Helmet alone does not make an app secure.
In the last chapter, we learned how middleware works and how to write our own.
But here's the thing: you don't need to build everything from scratch. Express has built-in middleware, and the ecosystem provides packages for common backend tasks.
Let's understand the important ones. To follow along, install the third-party packages:
npm install morgan cors helmet express-rate-limit1. Built-in Middleware ๐ฆ
Express provides middleware for handling common request data.
app.use(express.json());This parses incoming JSON request bodies and makes the data available through req.body.
Other useful built-in middleware:
| Middleware | What it handles |
|---|---|
express.json() | JSON request bodies, placed in req.body |
express.urlencoded({ extended: true }) | URL-encoded form data (classic HTML forms), placed in req.body |
express.static("public") | Serves static files such as images, CSS and HTML from a folder |
Here is the difference express.json() makes:
const express = require("express");
const app = express();
app.use(express.json());
app.post("/users", (req, res) => {
res.status(201).json({ received: req.body });
});
app.listen(3000);Key insight: Without express.json(), req.body is undefined, even if the client sent perfectly valid JSON.
2. Logging With Morgan ๐
How do you know which requests are reaching your server? Morgan logs incoming HTTP requests.
const morgan = require("morgan");
app.use(morgan("dev"));It logs details such as the HTTP method, URL, status code, and response time:
GET /api/users 200 4.512 ms - 25
GET /api/orders 404 1.873 ms - 149Useful for debugging and monitoring requests. It's the same idea as the request logger we wrote on Day 14, but ready-made and with several log formats (for example "dev" for development and "combined" for production-style logs).
3. CORS Middleware ๐
Your frontend runs on one origin and your backend on another. The browser may block frontend JavaScript from accessing the backend response unless the server allows that origin.
That's where CORS comes in.
const cors = require("cors");
app.use(cors({
origin: "http://localhost:5173" // your frontend's origin
}));Our backend runs on port 3000, so here we allow a frontend on a different port, such as a Vite dev server on 5173. Use your real frontend URL.
CORS controls which origins are allowed to access your resources through browsers.
Notice that the browser does the blocking. Tools like curl or Postman ignore CORS completely.
Important: CORS is not authentication or a replacement for backend authorization.
4. Helmet ๐ช
Your API should send appropriate HTTP security headers. Helmet helps by setting several security-related headers.
const helmet = require("helmet");
app.use(helmet());A few of the headers it handles:
| Header | What it does |
|---|---|
Content-Security-Policy | Limits where scripts, styles and images can load from |
Strict-Transport-Security | Tells browsers to use HTTPS only |
X-Content-Type-Options: nosniff | Stops browsers from guessing content types |
X-Frame-Options: SAMEORIGIN | Stops other sites from embedding your pages |
X-Powered-By | Removed, so you don't advertise that you use Express |
It provides useful security defaults, but it doesn't automatically make your application secure. You still need validation, authentication and authorization.
5. Rate Limiting ๐ฆ
What happens if someone sends thousands of requests to your API? Your server can become overloaded.
Rate limiting restricts how many requests a client can make within a defined time window.
const rateLimit = require("express-rate-limit");
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100
});
app.use(limiter);This example allows up to 100 requests per 15 minutes per client, based on the limiter's configured keying strategy (by default, the client's IP address). Once a client goes over the limit, it gets 429 Too Many Requests until the window resets.
6. Putting Them Together ๐
Here is a complete, runnable server.js with every piece in place:
const express = require("express");
const morgan = require("morgan");
const cors = require("cors");
const helmet = require("helmet");
const rateLimit = require("express-rate-limit");
const app = express();
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100
});
// A stub router standing in for your real API routes
const apiRoutes = express.Router();
apiRoutes.get("/users", (req, res) => {
res.json({ users: ["Dev", "Sahil"] });
});
app.use(express.json());
app.use(morgan("dev"));
app.use(cors({ origin: "http://localhost:5173" }));
app.use(helmet());
app.use(limiter);
app.use("/api", apiRoutes);
app.listen(3000, () => {
console.log("Server running on port 3000");
});Middleware order matters. Express executes middleware in the order you register it.
| Middleware | What it does | Why it is placed here |
|---|---|---|
express.json() | Parses JSON bodies into req.body | First, so everything after it can read req.body |
morgan("dev") | Logs each request | Early, so every request is logged, even ones rejected later |
cors(...) | Adds CORS headers and answers preflight requests | Before the routes, so every API response carries CORS headers |
helmet() | Sets security headers | Before the routes, so every response gets them |
limiter | Counts requests per client | Right before the routes, so excess requests never reach them |
app.use("/api", apiRoutes) | Your actual endpoints | Last, after the request has passed through the whole pipeline |
This is not the only valid order. Some teams register helmet() first, or put the rate limiter before body parsing so blocked requests do less work. What matters is choosing the order on purpose.
Common mistakes
- Forgetting
express.json(), then wondering whyreq.bodyisundefined. - Registering middleware after the routes, so it never runs for them.
- Thinking CORS protects your API from curl, Postman or other servers. It only guides browsers.
- Treating
helmet()as complete security. - Deploying behind a proxy without configuring
app.set("trust proxy", ...), so the rate limiter sees the proxy's IP instead of each client's.
7. Remember ๐ง
| Idea | In one line |
|---|---|
| Built-in middleware | Handles common Express tasks like parsing bodies |
| Third-party middleware | Saves you from reinventing common functionality |
| Morgan | Logs every request that reaches your server |
| CORS | Controls browser cross-origin access |
| Helmet | Adds security-related HTTP headers |
| Rate limiting | Helps protect APIs from excessive requests |
| Order | Express runs middleware in the order you register it |
A good backend isn't just about writing routes. It's also about building the right request-processing pipeline.
๐งช Try it yourself
- Save the code from section 6 as
server.jsand runnode server.js. - Run
curl -i http://localhost:3000/api/users. Spot the Helmet headers (Content-Security-Policy,X-Content-Type-Options), theAccess-Control-Allow-Originheader and the missingX-Powered-By. Check your terminal for Morgan's log line. - Change
limit: 100tolimit: 3, restart, and run:
for i in 1 2 3 4; do curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/api/users; doneYou should see 200 three times, then 429.
๐ฏ Day 15 in one line
Let built-in middleware handle common request data, use trusted packages for logging, CORS, security headers and rate limiting, and register them in a deliberate order, because a good backend is a well-built request-processing pipeline.
Written with DevJournal โ a learning journal that helps developers remember what they learn.
Start your own journal free