Backend Development with Node.js, Express, REST APIs & GraphQL
I build the server-side architecture that powers web applications, mobile apps, and SaaS platforms. Node.js and Express are my primary tools, and I design APIs using both REST and GraphQL depending on what the project actually needs. Every backend I ship is built for production. That means proper error handling, authentication, input validation, rate limiting, and architecture that can handle real traffic without falling over at 2 AM.
Node.js & Express: The Engine Room
Node.js is not just a trend. It is the runtime that powers backends for companies ranging from startups to Netflix and PayPal. I chose it years ago because it lets me write JavaScript across the entire stack, which means faster development, easier debugging, and a single language from the browser to the database. Express sits on top of Node as a minimal, unopinionated framework that gives me full control over routing, middleware, and request handling without the overhead of a bloated framework.
My Express applications follow a clean modular architecture. Routes, controllers, services, and data access layers are separated so that each piece can be tested independently and swapped out when requirements change. Middleware handles cross-cutting concerns like authentication, logging, CORS, and request validation before any business logic runs. This is not something you bolt on later. It is the foundation.
I use Node.js for everything from simple REST APIs serving a mobile app to complex event-driven systems that process webhooks, queue background jobs, and coordinate between multiple third-party services. The non-blocking I/O model makes it exceptionally good at handling concurrent connections, which matters when your application needs to serve hundreds or thousands of simultaneous users without spinning up expensive infrastructure.
If you have a project that needs a fast, reliable API or a backend that integrates with multiple services, Node.js and Express are almost always where I start. The ecosystem is mature, the tooling is excellent, and the deployment options are flexible enough to fit any budget.
API Architecture: REST vs GraphQL
Every backend I build is API-first. That means the server exposes clean, well-documented endpoints that any client can consume, whether that client is a React frontend, a mobile app, a third-party integration, or an internal tool. The question is always which API style fits the project.
REST is my default for most projects. It is simple, well-understood, and works perfectly when your data model maps cleanly to resources. A REST API for a project management tool might have endpoints like /api/projects, /api/projects/:id/tasks, and /api/users/:id. Each endpoint returns exactly what it says. The HTTP methods (GET, POST, PUT, DELETE) map directly to operations. Caching is straightforward. Any developer who touches your codebase will understand the API immediately.
GraphQL shines when the frontend has complex data requirements that would turn a REST API into a mess of custom endpoints and query parameters. Instead of hitting five different endpoints to assemble a dashboard view, the client sends a single query describing exactly what data it needs and the server returns precisely that. No over-fetching. No under-fetching. One round trip.
I implement GraphQL using Apollo Server on the backend, with properly defined schemas, resolvers, and data loaders to prevent the N+1 query problem that trips up most GraphQL implementations. I also set up query complexity analysis and depth limiting to prevent malicious or poorly written queries from hammering your database.
The honest answer is that most small to mid-size projects do not need GraphQL. REST handles them well and is easier to maintain. But when you have a data-rich application with multiple client types that each need different slices of the same data, GraphQL can dramatically simplify both your frontend and backend code.
When to Use REST vs GraphQL
| Factor | REST | GraphQL |
|---|---|---|
| Data complexity | Best for simple, resource-based data models with predictable access patterns | Best for deeply nested or interconnected data that varies by client view |
| Client types | Works well when one or two clients consume the API in similar ways | Ideal when mobile, web, and third-party clients each need different data shapes |
| Caching | HTTP caching works out of the box with ETags and Cache-Control headers | Requires application-level caching strategies like persisted queries |
| Learning curve | Low. Any developer can read and consume a REST API immediately | Moderate. Schema design and resolver patterns require upfront investment |
| Over-fetching | Common. Endpoints return fixed response shapes even when the client only needs a subset | Eliminated. Clients request exactly the fields they need |
| File uploads | Native multipart form support | Requires workarounds or separate REST endpoints for file handling |
| Real-time | Requires separate WebSocket implementation | Built-in subscription support for real-time data |
Authentication & Security
Authentication is one of those things that seems simple until you get it wrong. I implement JWT-based authentication for stateless API access, session-based auth for traditional web applications, and OAuth 2.0 flows for third-party integrations. The choice depends on your application architecture and security requirements.
For most API-driven applications, I use short-lived access tokens paired with secure HTTP-only refresh tokens. The access token handles authentication for individual requests without hitting the database on every call. The refresh token rotates on each use and is stored where JavaScript cannot access it, which prevents the most common token theft attacks.
Beyond authentication, every API I build includes input validation and sanitization on every endpoint, rate limiting to prevent abuse and brute force attacks, CORS configuration that only allows the domains that should be talking to your API, parameterized database queries to prevent SQL injection, and proper error responses that do not leak internal system details to potential attackers.
I also implement role-based access control when the application needs it. Not every user should be able to hit every endpoint. Permissions are enforced at the middleware level so that authorization logic stays consistent and centralized rather than scattered across individual route handlers.
Real-Time Features
Some applications need data to update in real time without the user refreshing the page. Live dashboards, chat systems, notification feeds, collaborative editing, and order tracking all require a persistent connection between the client and the server.
I build real-time features using WebSockets through Socket.io for bidirectional communication and Server-Sent Events for simpler one-way data streams. Socket.io handles the messy reality of real-time connections, including automatic reconnection, fallback transports for restrictive networks, and room-based broadcasting so you can push updates to specific groups of users without flooding everyone.
For applications that need real-time data combined with GraphQL, I implement GraphQL Subscriptions. These let the client subscribe to specific data changes and receive updates pushed from the server the moment something changes. A project management app might subscribe to task status changes so the board updates instantly when a team member moves a card.
Real-time features add complexity to your backend architecture, so I only recommend them when the user experience genuinely benefits. A live chat system needs WebSockets. A contact form does not. I will be honest about what your application actually requires.
Scaling for Production Traffic
Building a backend that works in development is easy. Building one that holds up under production traffic with real users, unpredictable load spikes, and zero tolerance for downtime is a different discipline entirely.
I design backends with horizontal scaling in mind from the start. That means stateless application servers that can be replicated behind a load balancer without session affinity issues. It means caching frequently accessed data in Redis so your database is not getting hammered with the same queries on every request. It means offloading slow operations like email sending, image processing, and report generation to background job queues so API responses stay fast.
Database performance is usually the first bottleneck. I design schemas with proper indexing, write efficient queries, and set up connection pooling from day one. You can read more about my approach on the databases page. For read-heavy applications, I implement caching layers that keep your database load manageable as traffic grows. When the data model calls for it, I use both SQL databases like PostgreSQL for relational data and NoSQL options like MongoDB for document-oriented workloads.
Monitoring and logging are part of every production deployment. Structured logging with correlation IDs lets me trace a request from the frontend through every service it touches. Health check endpoints, error tracking, and performance metrics give you visibility into how your backend is actually performing, not just whether it is running.
I have built backends that serve thousands of daily active users and handle traffic spikes without breaking a sweat. The key is not overengineering from the start but architecting in a way that gives you a clear, affordable path from your current scale to your next milestone.
Laravel: The PHP Framework I Reach For
Laravel is the most productive way to build a serious PHP application, and it is what I use when a project needs a full backend framework rather than the hand-rolled architecture I favor for lightweight sites. If your business already runs on PHP β and given that PHP powers most of the web, there is a good chance it does β Laravel means new features get built on a modern, well-tested foundation instead of accumulating another layer of custom includes.
What makes it worth the dependency is how much is already solved and solved well. Eloquent handles the ORM layer with relationships that read like plain English. Migrations put your database schema under version control, so deploying a change to production is a command instead of a pile of hand-run SQL. Sanctum and Passport cover API token authentication and OAuth. Queues move slow work like PDF generation, image processing, and outbound email off the request cycle. The scheduler replaces a mess of crontab entries with one line and readable PHP. Every one of those is a component I would otherwise have to build, test, and maintain by hand.
Laravel also pairs naturally with the front ends I build. Blade handles server-rendered views when a page does not need to be an application. Inertia lets me put React or Vue on top of Laravel routes without building and versioning a separate API. And when a project genuinely needs a decoupled front end, Laravel makes a clean JSON API with resource transformers and proper pagination.
The honest tradeoff is weight. For a five-page marketing site with a contact form, Laravel is more framework than the problem deserves and I will build it in plain PHP instead. But for a customer portal, a booking system, an internal CRM, a subscription product, or any application with real business logic and multiple user roles, Laravel is where I start.
When Laravel Is the Right Call
- Customer portals, dashboards, and internal tools with multiple user roles and permissions
- Subscription products that need billing, invoicing, and recurring jobs
- Replacing an aging custom PHP codebase without leaving the PHP ecosystem
- Applications where the team maintaining it after handoff already knows PHP
- Projects needing queues, scheduled jobs, and transactional email out of the box
Django, FastAPI & NestJS
Express is deliberately unopinionated, which is exactly right when I want full control and exactly wrong when a project would benefit from structure that is already decided. Three frameworks fill that gap depending on the language the project belongs in.
Django is the batteries-included choice for Python backends. Its ORM, migration system, and authentication are mature and dependable, but the feature that repeatedly earns its keep on client projects is the admin interface. Django generates a genuinely usable CRUD backend from your models, which means a business gets a working internal tool for managing their data on day one instead of week six. For content-heavy applications, internal systems, and anything where staff need to administer records directly, that alone can justify the framework choice.
FastAPI is what I use for Python services that exist to serve an API, and it is my default for anything AI-related. It is built on async Python and Pydantic, so request and response validation comes from type hints rather than hand-written checks, and it generates OpenAPI documentation automatically. Because the entire AI and machine learning ecosystem is Python-first, FastAPI is usually the shortest path between a model, a vector database, and a production HTTP endpoint that a web or mobile client can actually call.
NestJS brings that same structural discipline to TypeScript. It uses modules, dependency injection, decorators, and a strongly typed request pipeline β an architecture that will feel immediately familiar to anyone from a Spring or .NET background. On a large Node codebase that several developers will touch, NestJS prevents the slow slide into a folder of loosely related route files that Express projects are prone to.
How I Pick Between Them
- Express β lean APIs, full control, small services and prototypes
- NestJS β large TypeScript codebases and teams that need enforced structure
- Django β Python applications that need an admin interface and a mature ORM
- FastAPI β Python API services, AI endpoints, and anything performance-sensitive and async
- Laravel β full-featured PHP applications with business logic and user roles
Payments, Queues & Webhooks
This is the unglamorous layer where real applications break, and it is the part I care most about getting right. When money moves, a mistake is not a bug report β it is a double charge, a customer who paid and got nothing, or an order that vanished.
Stripe is my default payment processor for subscriptions, one-time payments, invoicing, and marketplace payouts. I implement it with Checkout or Payment Elements so card data never touches your server, which keeps your PCI scope minimal. Subscriptions get proper handling for trials, proration, upgrades, downgrades, failed payments, and dunning. I also wire PayPal and Square when your customers expect them.
Webhooks are how the rest of the internet tells your application something happened, and almost every integration I inherit gets them wrong. Every webhook endpoint I build verifies the cryptographic signature before trusting a single byte, returns a 200 immediately and does the actual work asynchronously, and treats delivery as at-least-once. That last point matters: providers retry, so the same event will arrive twice. Every handler is idempotent, keyed on the provider's event ID, so a duplicate delivery is recognized and discarded instead of charging a customer again.
Queues are what make that possible. Generating a PDF, syncing to a CRM, sending a receipt, provisioning a seat, calling a slow third-party API β none of that belongs in the request cycle where a timeout loses the work. I use Redis-backed queues with BullMQ on Node, Laravel Horizon on PHP, or Celery on Python, with retries, exponential backoff, and a dead letter queue. Jobs that exhaust their retries get parked for a human to inspect rather than disappearing into a log file nobody reads.
What I Build Into Every Integration
- Signature verification on every inbound webhook before any processing
- Idempotency keys so duplicate deliveries never double-charge or double-ship
- Fast 200 responses with the real work handed to a background queue
- Retries with exponential backoff and a dead letter queue for failures
- An audit trail of every event received, so disputes can be reconstructed later
Need a Backend That Scales?
Book a free discovery call to discuss your API architecture and backend needs.
Book a Call