Server-Side Logic
The backend is the authoritative brain of any software project. It enforces the rules of the business, processes raw data, and guarantees that every action strictly follows the defined system constraints. While the frontend handles the user experience, the server-side logic handles the absolute truth. A poorly structured backend quickly degrades into an unmaintainable web of interconnected rules, making it dangerous to add new features or update existing ones.
Separation of concerns: Controllers and services
Professional backend architecture isolates different types of logic into distinct layers. Mixing HTTP request handling, database queries, and business rules in a single file makes the code impossible to test and reuse.
- Controllers: These act as the traffic directors of the application. They receive the incoming HTTP request, extract the necessary parameters, and return the final HTTP response. They should contain zero business logic.
- Services: This is where the actual work happens. A service function calculates totals, checks inventory, or coordinates multiple database calls. Services are completely agnostic of HTTP, making them easy to test or trigger from background jobs.
- Data Access Layer: This layer interacts directly with the database. It abstracts the SQL queries or Object-Relational Mapping (ORM) methods away from the service layer, allowing you to swap database technologies without rewriting your business rules.
Data validation and sanitization
The server must treat all incoming data as potentially hostile. You can never assume that the client has sent the correct data types or values, even if the frontend has its own validation in place.
- Validation: Ensuring the data matches the expected shape and type. If an age is required, it must be a strictly positive integer.
- Sanitization: Cleaning the data to prevent injection attacks. This involves stripping HTML tags from text inputs or escaping special characters to prevent cross-site scripting (XSS) when the data is rendered later.
- Schema libraries: Tools like Zod or Joi provide a structured way to enforce these data rules before the request ever reaches the controller logic, immediately rejecting invalid payloads.
Learning Point: UI validation is for user experience, but server validation is for security and data integrity. You must implement both, but the server is the final line of defense.
Managing asynchronous processing
Not all logic should execute during the standard HTTP request lifecycle. If a task takes longer than a few hundred milliseconds, it will block the client and degrade the user experience.
- Message queues: Systems like RabbitMQ or Redis allow the main server to offload heavy tasks. The server instantly returns a “202 Accepted” status, and a separate worker process handles the actual job in the background.
- Cron jobs: Scheduled tasks that run at specific intervals, such as generating weekly financial reports or clearing expired authentication sessions from the database.
- Webhooks: Asynchronous callbacks that notify other systems when a specific event occurs, such as alerting an inventory system after a successful payment transaction.
Designing for idempotency
Network connections drop unexpectedly. Clients click submission buttons twice. Systems must handle duplicate requests gracefully without causing unintended side effects, such as charging a credit card multiple times.
Idempotency is the property of an operation where applying it multiple times yields the same result as applying it once.
- Idempotency keys: A unique identifier sent by the client for critical actions like payments. The server checks if this key has been processed before executing the logic again, returning the cached success response if it has.
- Safe methods: HTTP GET, PUT, and DELETE methods are naturally idempotent by design. Deleting a resource twice should just return a success or a 404 Not Found error, rather than crashing the system.
- Atomic transactions: Wrapping multiple database changes in a single transaction ensures that either all changes succeed or none do. This prevents partial data corruption if the server crashes halfway through a multi-step process.
/**
* A service layer function handling a financial transaction.
* It uses a database transaction to ensure idempotency and atomic updates.
*/
const processRefund = async (orderId, refundAmount, idempotencyKey) => {
// 1. Check if this exact request was already processed
const existingRefund = await db.refunds.findByKey(idempotencyKey);
if (existingRefund) {
return existingRefund; // Return the cached result safely
}
// 2. Start an atomic database transaction
return await db.transaction(async (trx) => {
const order = await trx.orders.findById(orderId);
if (order.status !== 'completed') {
throw new Error("Only completed orders can be refunded.");
}
// 3. Execute the core business logic
const refund = await trx.refunds.create({
orderId,
amount: refundAmount,
idempotencyKey
});
await trx.orders.updateStatus(orderId, 'refunded');
return refund; // Transaction automatically commits here
});
};
Managing third-party integrations
Modern applications rarely exist in isolation. They rely on external APIs for payments, emails, and cloud storage. Integrating these services directly into your business logic creates a brittle architecture.
- The adapter pattern: Wrapping external SDKs in your own internal interface. If you switch your payment provider from Stripe to PayPal, you only update the adapter file, leaving the core business logic completely untouched.
- Timeouts: Always enforce strict time limits when calling external APIs. If an external service hangs indefinitely, your server will exhaust its available connection pool waiting for a response.
- Fallbacks: Defining a secondary path if the primary integration fails, such as sending an SMS text message if a push notification service goes down.
State machines for complex workflows
Relying on simple boolean flags like is_active or is_published becomes unmanageable as business logic grows in complexity.
A State Machine is a behavioral model that defines a strict set of states and the allowed transitions between them.
- Explicit states: An e-commerce order might move from “pending” to “processing” to “shipped” to “delivered.”
- Guarded transitions: Logic that prevents an order from moving directly from “pending” to “delivered” without passing through the required intermediate steps.
- Predictability: State machines eliminate impossible data combinations, such as a user being categorized as both “banned” and “premium_active” at the exact same time.
Note: Use enumerations (Enums) in your database and codebase to enforce these strict states. Plain text strings are prone to typos that can silently break complex business rules.
By structuring your server-side logic around these architectural boundaries, you create a backend engine that is resilient to failure, easy to test, and capable of safely scaling alongside your business requirements.
Popular Guides