05
Backend
APIs, routing, authentication, business logic. How I went from "what is an endpoint" to building 100+ API routes.
ExpressFastifyTypeScript
Building APIs sounded intimidating before I started. What is an endpoint? What is a route? What does REST mean? I had no idea. Now I have built over 100 API endpoints across multiple projects, in three different frameworks, in two different programming languages. The approach was always the same: I described what data I needed to send or receive, and Claude built the API layer.
In SMTPCloud Dashboard, the backend runs on Express.js with TypeScript. It has over 50 endpoints handling everything from user authentication to campaign management to email analytics. Users can create campaigns, upload recipient lists, schedule sends, track opens and clicks, manage domains, generate API keys, and view detailed reports. Each of these features has multiple API routes behind it. The Express server connects to PostgreSQL and handles all the business logic.
Quality Monitoring uses Fastify, which is a faster alternative to Express. It has 35+ endpoints and does something I found fascinating when I first built it: it syncs data with Google Sheets on a schedule. There are cron jobs that pull data from Google Sheets every 10 minutes, process it, and update the local database. There are also daily report jobs that aggregate data and generate summaries automatically. I described the sync requirements to Claude — "I need to pull data from this Google Sheet every 10 minutes and update my database" — and it built the entire cron system.
LeadTool is built with Python and FastAPI, which is a completely different ecosystem. It uses async request handling, SQLAlchemy for database access, and Celery with Redis for background job processing. This was my first Python backend, and the transition was surprisingly smooth because the pattern is the same regardless of language: describe what the API should do, and Claude writes it.
The pattern for building any API endpoint is straightforward. You describe what data you need. "I need an endpoint that returns a list of campaigns for the logged-in user, with pagination, and the ability to filter by status and date range." Claude creates the route, the controller function that handles the request, the service layer that contains the business logic, and the database query that fetches the data. Routes point to controllers, controllers call services, services talk to the database. That layered architecture — routes, controllers, services, database — keeps things organized even when you have 50+ endpoints.
Authentication was something I thought would be incredibly complicated. It is not, when you describe it clearly. 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." JWT tokens are like digital ID cards — when a user logs in, the server gives them a token, and they send that token with every subsequent request to prove who they are. Claude set up the entire auth system: password hashing, token generation, token verification middleware, and role-based access control that checks permissions on every protected endpoint.
Middleware is a concept that sounds abstract but is actually simple in practice. It is code that runs before your main endpoint logic. Need to check if a user is logged in before they access a route? That is auth middleware. Need to limit how many requests someone can make per minute so they do not abuse your API? That is rate limiting middleware. Need to allow your frontend running on localhost:3000 to talk to your backend on localhost:5000? That is CORS middleware. 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.
Validation is critical and Claude handles it well. When someone sends data to your API — a form submission, a file upload, a JSON payload — you need to verify that the data is correct before processing it. 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 and returns clear error messages if something is malformed or missing.
Error handling used to trip me up constantly. What happens when the database is down? When a user sends invalid data? When an external API like Google Sheets times out? Claude builds error handling into every endpoint, but I learned to be explicit about edge cases: "If the campaign is not found, return a 404 with a clear message. If the user does not have permission to view it, return 403. If the database query fails unexpectedly, return 500 and log the full error for debugging." Being specific about error scenarios makes your API much more reliable and much easier to debug when something goes wrong.
Background jobs changed how I think about what applications can do. In Quality Monitoring, node-cron runs scheduled tasks: syncing data from Google Sheets every 10 minutes, generating daily summary reports at midnight, cleaning up old temporary data weekly. In LeadTool, Celery workers process long-running tasks in the background so the API responds instantly to the user while the heavy computation happens separately. I described the need simply — "I want the Google Sheets sync to run automatically every 10 minutes without anyone triggering it manually" — and Claude set up the entire scheduling and job processing system.
I learned RESTful API design not from reading specifications or taking courses, but from building real endpoints for real features and seeing the patterns emerge naturally. GET to fetch data, POST to create, PUT to update, DELETE to remove. Consistent URL patterns like /api/campaigns, /api/campaigns/:id, /api/campaigns/:id/recipients. Proper status codes: 200 for success, 201 for created, 400 for bad input, 401 for not logged in, 404 for not found. After building enough endpoints, these conventions become second nature — not because I memorized them, but because I saw Claude apply them consistently across dozens of real features.