Phase 5: Backend Development
- Authors

- Name
- Wan Ilhami
- @wan-ilhami-43515a184
Welcome to the hidden engine room of the internet! If frontend development is the beautiful dining area of a restaurant where customers sit, browse the menu, and place orders, then backend development is the chaotic, highly-organized kitchen in the back. The customers (users) never see the kitchen, but without it, no food (data) ever gets served.
In this textbook-level guide, we will break down backend development from the ground up. We won't just cover theories; we will build practical, real-world implementations step-by-step.
1. The Anatomy of a Backend
A backend system typically consists of three major components:
- The Server: A computer that listens for incoming requests (orders from the waiters).
- The Application Logic: The code you write (the chefs) that processes the request, calculates things, and decides what to do.
- The Database: The persistent storage (the refrigerator and pantry) where all the ingredients (data) are kept securely.
When a user clicks "Log In" on a website, the frontend sends an HTTP Request to the backend. The backend reads the username and password, checks the database to see if they match, and sends back an HTTP Response (e.g., "Welcome back!" or "Invalid password").
2. Demystifying HTTP and REST APIs
The universal language that the frontend and backend use to communicate is called HTTP (Hypertext Transfer Protocol).
An API (Application Programming Interface) is a set of rules that dictates how this communication happens. The most common architectural style for web APIs is REST (Representational State Transfer).
In a REST API, everything is treated as a "Resource" (like a User, a Post, or a Product), and you use standard HTTP methods to perform CRUD operations:
- Create (POST): Add a new resource.
- Read (GET): Retrieve an existing resource.
- Update (PUT / PATCH): Modify a resource.
- Delete (DELETE): Remove a resource.
Step-by-Step: Building a REST API in Node.js & Express
Let's build a simple backend to manage a list of "Books". We will use Node.js and a popular framework called Express.
Step 1: Setup First, initialize a project and install Express:
npm init -y
npm install express
Step 2: The Server Code (server.js)
const express = require('express');
const app = express();
// Middleware to parse incoming JSON data
app.use(express.json());
// A simple in-memory database (an array)
let books = [
{ id: 1, title: "The Hobbit", author: "J.R.R. Tolkien" }
];
// 1. READ (GET all books)
app.get('/api/books', (req, res) => {
res.status(200).json(books);
});
// 2. CREATE (POST a new book)
app.post('/api/books', (req, res) => {
const newBook = {
id: books.length + 1,
title: req.body.title,
author: req.body.author
};
books.push(newBook);
// 201 Created status code
res.status(201).json(newBook);
});
// 3. READ ONE (GET a specific book by ID)
app.get('/api/books/:id', (req, res) => {
const bookId = parseInt(req.params.id);
const book = books.find(b => b.id === bookId);
if (!book) {
return res.status(404).json({ error: "Book not found" });
}
res.status(200).json(book);
});
// 4. DELETE (Remove a book)
app.delete('/api/books/:id', (req, res) => {
const bookId = parseInt(req.params.id);
books = books.filter(b => b.id !== bookId);
res.status(200).json({ message: "Book deleted successfully" });
});
// Start the server on port 3000
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
With just 50 lines of code, you have a fully functional CRUD API!
3. Handling File Uploads
Handling plain text (JSON) is easy. Handling binary files (like images, PDFs, or videos) requires a different approach. When submitting files, the frontend must use a format called multipart/form-data.
On the backend, we need specialized middleware to read this binary stream. In Express, the industry standard is multer.
The Golden Rule of File Uploads
Never save user-uploaded files permanently on your web server's hard drive. Why?
- If your server crashes and needs to be replaced, the files are gone.
- If you scale up to 5 servers, a file uploaded to Server A won't be accessible on Server B.
The Solution: Stream uploads directly to a dedicated Cloud Storage service like AWS S3, and save the resulting URL in your database.
Implementation: Multer + AWS S3
const express = require('express');
const multer = require('multer');
const AWS = require('aws-sdk');
const app = express();
// Configure Multer to store the file temporarily in memory (RAM)
const upload = multer({ storage: multer.memoryStorage() });
// Configure AWS SDK with your credentials
const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_KEY,
});
// The endpoint expects a file uploaded in a field named 'profilePicture'
app.post('/api/upload', upload.single('profilePicture'), async (req, res) => {
try {
const file = req.file; // The binary file data from Multer
if (!file) {
return res.status(400).json({ error: "Please upload a file" });
}
// Prepare parameters for S3
const s3Params = {
Bucket: 'my-app-image-bucket',
Key: `${Date.now()}_${file.originalname}`, // Unique file name
Body: file.buffer, // The actual binary data
ContentType: file.mimetype, // e.g., 'image/jpeg'
};
// Upload to S3
const s3Response = await s3.upload(s3Params).promise();
// S3 returns the public URL of the uploaded file
const fileUrl = s3Response.Location;
// TODO: Save `fileUrl` to your database attached to the user's profile
res.status(200).json({
message: "File uploaded successfully!",
url: fileUrl
});
} catch (error) {
console.error(error);
res.status(500).json({ error: "Server error during upload" });
}
});
4. Background Jobs & Queues
Imagine a user clicks a button to "Generate Annual Financial Report." Processing this report requires crunching millions of database rows, compiling a PDF, and emailing it. This might take 45 seconds.
If you handle this inside the normal HTTP request/response cycle, the user's browser will sit there spinning for 45 seconds. Worse, the browser might time out, or the server might run out of memory if 100 users click the button at the same time.
The Solution: Message Queues
We use a pattern called Asynchronous Background Jobs.
- The user requests the report.
- The backend immediately responds:
HTTP 202 Accepted: "Your report is generating. We will email you when it's done!" - Behind the scenes, the backend pushes a "Job" onto a Queue (like a to-do list).
- A completely separate, specialized server (a "Worker") reads the queue, pulls the job, and spends 45 seconds generating the report without affecting the main web server.
A popular stack for this in Node.js is Redis (an incredibly fast in-memory database) and BullMQ (a queue library).
Implementation: Simple Queue
// worker.js (Running on a separate server)
const { Worker } = require('bullmq');
// This worker constantly listens to the "ReportQueue"
const worker = new Worker('ReportQueue', async (job) => {
console.log(`Starting job ${job.id}: Generating report for user ${job.data.userId}...`);
// Simulate a heavy task taking 10 seconds
await new Promise(resolve => setTimeout(resolve, 10000));
console.log(`Finished job ${job.id}! Report sent via email.`);
}, {
connection: { host: 'localhost', port: 6379 } // Connect to Redis
});
// server.js (Your main web server)
const express = require('express');
const { Queue } = require('bullmq');
const app = express();
// Connect to the same Redis queue
const reportQueue = new Queue('ReportQueue', {
connection: { host: 'localhost', port: 6379 }
});
app.post('/api/generate-report', async (req, res) => {
const userId = 123;
// Push the heavy task to the queue immediately
await reportQueue.add('FinancialReport', { userId: userId });
// Respond to the user instantly!
res.status(202).json({
message: "Report generation started. We will email you when it is ready."
});
});
5. Security & Authentication Fundamentals
A backend is only as good as its security. If you don't lock your API doors, anyone can delete your data.
Hashing Passwords
Never store plain-text passwords in your database. If your database is hacked, the attacker will have everyone's passwords. Instead, use a cryptographic hashing algorithm like bcrypt. Hashing is a one-way street: you can turn "password123" into an unreadable string of gibberish, but you can never turn the gibberish back into "password123".
const bcrypt = require('bcrypt');
// When user registers:
const plainTextPassword = "mySuperSecretPassword";
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(plainTextPassword, saltRounds);
// Save `hashedPassword` to the DB
// When user logs in:
const userAttempt = "mySuperSecretPassword";
const isMatch = await bcrypt.compare(userAttempt, hashedPassword);
if (isMatch) {
// Let them in
}
JSON Web Tokens (JWT)
Once a user logs in, how does the server remember who they are on subsequent requests? HTTP is "stateless," meaning it has a memory of a goldfish.
To solve this, we give the user a digital ID card called a JWT.
- User logs in with email/password.
- Server verifies them and creates a JWT (a long string of characters cryptographically signed by the server).
- Server sends the JWT to the frontend.
- For every future request (e.g., getting profile data), the frontend attaches the JWT in the
Authorizationheader. - The server inspects the JWT, validates the signature, and knows exactly who the user is without having to look them up in the database again!
Conclusion
Backend development is a vast, logical landscape. By mastering HTTP APIs, understanding how to offload files and heavy tasks, and securing your endpoints, you transition from building simple web pages to engineering robust, highly-available software systems. Keep experimenting, keep breaking things, and keep checking those server logs!