Modules/Backend
05

Backend

APIs, routing, authentication, business logic. How I went from "what is an endpoint" to building 100+ API routes.

ExpressFastifyTypeScript
What IS a backend?

Let me start from zero. The backend is the invisible part of any application. When you open a website, you see buttons and forms and images — that is the frontend. But when you click "Login" and type your password, something needs to check if that password is correct. That something is the backend. It is the brain behind the scenes.

The frontend is the face. The backend is everything behind it — checking passwords, saving data, sending emails, processing payments, crunching numbers. You never see it directly, but without it, the website is just a pretty picture that does nothing.
APIs explained: the waiter analogy

Your frontend needs data. Where does it come from? The backend. But they do not talk to each other directly — they communicate through an API (Application Programming Interface).

Think of it like a restaurant. The customer (frontend) wants food. The kitchen (database) has the ingredients. But the customer does not walk into the kitchen. There is a waiter (the API) who takes the order, brings it to the kitchen, and delivers the food back to the table.

When your frontend needs a list of campaigns, it sends a request to the API: "Give me all campaigns for this user." The API goes to the database, gets the data, formats it nicely, and sends it back. That is literally all an API does — it is the middleman between what the user sees and where the data lives.
Express.js — my first backend framework

Express.js is a Node.js framework for building APIs. It is the most popular one, it is simple, and it is what I used for SMTPCloud Dashboard.

I told Claude: "I need an API that handles user login, returns a JWT token, and has endpoints for creating campaigns, listing campaigns, and tracking email opens." That is it. Claude built the Express server with all those routes. I did not need to understand how Express works internally — I needed to know what my API should do.

The SMTPCloud backend has 50+ endpoints handling everything from authentication to campaign management to real-time analytics. Every single one was built by describing what it should do, not how to code it.
What a typical API endpoint looks like

Let me demystify this. An API endpoint is just a URL that does something when you call it. Here is the concept:

- GET /api/campaigns — returns a list of all campaigns
- GET /api/campaigns/123 — returns details for campaign 123
- POST /api/campaigns — creates a new campaign
- PUT /api/campaigns/123 — updates campaign 123
- DELETE /api/campaigns/123 — deletes campaign 123

That is it. GET means "give me data." POST means "create something new." PUT means "update something existing." DELETE means "remove something." These are the four main operations, and they cover almost everything your app will ever need to do.

When your frontend shows a table of campaigns, it calls GET /api/campaigns. When a user fills out the "Create Campaign" form and clicks submit, the frontend calls POST /api/campaigns with the form data. Simple as that.
Controllers vs Services — the receptionist and the specialist

As your backend grows, you need some organization. The pattern I use everywhere is controllers and services.

Controllers handle the HTTP request. They are like a receptionist — they receive the incoming request, figure out what is being asked, and hand it off to the right person. The controller says: "OK, you want to create a campaign. Let me pass this to the campaign service."

Services handle the business logic. They are the specialists who actually do the work — validate the data, talk to the database, apply business rules, and return the result.

Why separate them? Because it keeps things clean. The controller does not care about database queries. The service does not care about HTTP status codes. Each piece has one job. When your backend has 50+ endpoints, this separation is what keeps you sane.
JWT Authentication — the wristband at a club

When you log into a website, the server needs to remember who you are. It does this with JWT tokens (JSON Web Tokens). Here is the analogy:

Imagine a club. When you walk in, the bouncer checks your ID and gives you a wristband. For the rest of the night, you do not need to show your ID again — you just flash the wristband. The bartender sees the wristband and knows you are allowed to be there.

JWT works the same way. When you login, the server checks your email and password. If they are correct, it gives you a token (the wristband). Every request after that, your frontend sends the token along. The server checks the token and knows who you are without asking for your password again.

I told Claude: "I need login with email and password. There should be two roles: admin and client. Admins can see everything, clients can only see their own data. Use JWT tokens." Claude set up the entire auth system: password hashing, token generation, token verification, and role-based access control.
Database queries — the backend talks to PostgreSQL

The backend is the only thing that talks to the database. The frontend never touches the database directly — that would be a massive security risk.

When the frontend asks for "all campaigns for user 5," the backend takes that request, writes a database query like SELECT * FROM campaigns WHERE user_id = 5 ORDER BY created_at DESC, runs it against PostgreSQL, gets the results, formats them into JSON, and sends them back to the frontend.

I do not write most of these queries by hand. I describe what data I need — "get all campaigns for the logged-in user, sorted by date, with the total recipient count included" — and Claude writes the query. After seeing enough queries, you start to understand the SQL patterns naturally.
Middleware — the security guard at the door

Middleware is code that runs before every request reaches your endpoint. Think of it as a security guard at the door of a building.

Before anyone enters, the guard checks: Do you have a valid badge? Are you on the list? Are you carrying anything suspicious?

In backend terms:
- Auth middleware — checks if you have a valid JWT token. No token? 401 Unauthorized. Get out.
- Rate limiting middleware — checks if you are making too many requests. More than 100 per minute? 429 Too Many Requests. Slow down.
- CORS middleware — checks if your frontend is allowed to talk to this backend. Different domain? Only if it is on the approved list.
- Logging middleware — writes down every request that comes in, for debugging later.

Every time I needed one of these, I just described the requirement: "Add rate limiting so no IP can hit my API more than 100 times per minute." Claude added the middleware and wired it up.
Real example: How SMTPCloud handles sending a campaign

Let me walk you through a real flow. A user clicks "Send Campaign" in the SMTPCloud dashboard. Here is what happens behind the scenes:

1. The frontend sends a POST /api/campaigns/123/send request with the JWT token
2. Auth middleware checks the token — is this user real? Are they allowed to send campaigns?
3. The controller receives the request and calls the campaign service
4. The service checks: Does campaign 123 exist? Does it belong to this user? Does it have recipients? Is the sending domain verified?
5. If everything checks out, the service creates a background job to send the emails (you cannot send 10,000 emails in a single request — it would time out)
6. The API responds immediately: "Campaign queued for sending"
7. In the background, the email queue processes recipients one by one, respecting rate limits per domain
8. Each send result (delivered, bounced, etc.) gets recorded in the database
9. The frontend polls for status updates and shows progress in real time

That entire flow — from button click to emails landing in inboxes — was built by describing each step to Claude.
Error handling — what happens when things go wrong

If something goes wrong, the backend needs to say what happened clearly. There is a standard system for this using HTTP status codes:

- 200 = Success. Here is your data.
- 201 = Created. The new thing was made successfully.
- 400 = Bad request. You sent invalid data.
- 401 = Not authorized. You are not logged in or your token expired.
- 403 = Forbidden. You are logged in but you do not have permission for this.
- 404 = Not found. That campaign/user/resource does not exist.
- 429 = Too many requests. You are hitting the API too fast.
- 500 = Server error. Something broke on our end.

I learned to be explicit with Claude about error scenarios: "If the campaign is not found, return 404 with a clear message. If the user does not own it, return 403. If the database fails, return 500 and log the full error." Being specific about errors makes your API reliable and easy to debug.
Background jobs — because some things cannot happen in real time

Some things are too slow to do while the user waits. Sending 10,000 emails? That could take minutes. Processing a large CSV file? Could take 30 seconds. Generating a complex report? Maybe 10 seconds.

You cannot make the user stare at a loading spinner for minutes. Instead, you use background jobs. The user clicks "Send Campaign" and the API responds immediately: "Got it, we are working on it." The actual work happens in the background.

In Quality Monitoring, I use node-cron for scheduled background tasks. In LeadTool, I use Celery with Redis for job queues. The concept is the same: the user triggers something, the API says "acknowledged," and a background worker handles the heavy lifting.
Cron jobs from Quality Monitoring — automated tasks on a schedule

Quality Monitoring has several cron jobs that run automatically:

- Every 10 minutes: sync data from 7 different Google Sheets into the local database
- Every morning at 8am: send a daily summary email to team leads and managers
- Every Monday morning: generate a weekly performance report
- Every night at midnight: clean up temporary data and aggregate statistics

I described these to Claude exactly like that: "Every 10 minutes, pull data from this Google Sheet and update the database. Every morning, send a summary email." Claude set up node-cron to handle the scheduling. The jobs run silently in the background, and the team gets fresh data and reports without anyone lifting a finger.
Validation with Zod — making sure the data is correct

When someone sends data to your API — a form submission, a JSON payload — you need to check it before doing anything with it. What if the email field is empty? What if the date is in the wrong format? What if someone sends a string where a number should be?

I use Zod for this in TypeScript projects. I describe what the data should look like: "The campaign creation endpoint should require a name (string, not empty), a subject line (string, max 200 characters), and an optional scheduled send date (valid date in the future)."

Claude creates the Zod schema that validates every incoming request. If something is wrong, the API returns a clear error: "name is required" or "scheduled date must be in the future." The invalid data never reaches your database. It gets caught at the door.
Different backend frameworks: Express vs Fastify vs FastAPI

I have used three different backend frameworks across my projects:

- Express.js (Node.js) — used in SMTPCloud Dashboard. The most popular, the most documented, the most beginner-friendly. If you are starting out, this is the one.
- Fastify (Node.js) — used in Quality Monitoring. Faster than Express, with built-in validation and serialization. I switched to it when I wanted better performance.
- FastAPI (Python) — used in LeadTool. Python's answer to Express. Great for data-heavy applications.

Same concept, different tools. All three do the same thing: receive HTTP requests, process them, talk to a database, and return responses. I used all three depending on the project's needs. The skill of describing what your API should do transfers perfectly across all of them.
File uploads and S3 storage

Users need to upload files? Images for email templates, CSV files with recipient lists, attachments? Those files need to go somewhere. That somewhere is usually AWS S3 — a cloud storage service that is cheap, reliable, and scales infinitely.

I told Claude: "Users need to upload CSV files with recipient lists. Store them in S3, parse the CSV, and import the recipients into the database." Claude set up the S3 integration, the file upload endpoint, the CSV parsing logic, and the database import. The whole flow: user picks a file, frontend uploads it to the backend, backend stores it in S3, parses it, and imports the data.

For email template images, same pattern: upload to S3, get a public URL, use that URL in the email HTML.
Rate limiting — protecting your API from abuse

Do not let someone call your API 1,000 times per second. Whether it is a malicious attack, a buggy script, or an overeager user, you need limits.

Rate limiting means: "This IP address can make 100 requests per minute. After that, they get a 429 Too Many Requests response until the minute resets."

I told Claude: "Add rate limiting. 100 requests per minute per IP for general endpoints. 5 requests per minute for the login endpoint (to prevent brute force password guessing). 10 requests per minute for the email sending endpoint." Different limits for different endpoints based on how sensitive or expensive they are.
The real scale: production backends in numbers

- SMTPCloud Dashboard: 50+ endpoints, Express.js, handles user auth, campaign management, email sending, analytics, domain verification, API key management, billing
- Quality Monitoring: 35+ endpoints, Fastify, handles issue tracking, Google Sheets sync, automated reports, team management, AI-powered insights
- LeadTool: 25+ endpoints, FastAPI (Python), handles lead management, duplicate detection, bulk imports, status workflows

That is over 110 API endpoints across three projects, in two programming languages, using three different frameworks. Every single one was built by describing what it should do. I never memorized an Express API or a Fastify plugin. I described the behavior I needed and Claude wrote the code.
RESTful design — patterns that emerged naturally

I learned RESTful API design not from reading specifications, but from building real endpoints and seeing the patterns emerge. After building enough endpoints, these conventions become second nature:

- Consistent URL patterns: /api/campaigns, /api/campaigns/:id, /api/campaigns/:id/recipients
- GET to fetch, POST to create, PUT to update, DELETE to remove
- Proper status codes: 200, 201, 400, 401, 404, 500
- Pagination for large lists: ?page=1&limit=20
- Filtering: ?status=active&dateFrom=2024-01-01
- Always return JSON with a consistent structure

I did not memorize these rules. I saw Claude apply them consistently across dozens of real features, and they stuck. That is the power of learning by building — the patterns become instinct.