Backend Development

Chapter 4
Backend Frameworks and Application Structure

Certificate in Full stack programming
Backend Development   •   Certificate

Backend Frameworks and Application Structure

Introduction

A backend framework is a software platform that provides reusable structures, conventions, libraries, and tools for building server-side applications. Instead of implementing HTTP parsing, routing, request validation, error handling, configuration, and database integration from the beginning, developers use a framework to organize these responsibilities consistently.

Frameworks do not eliminate the need for backend knowledge. They make important design decisions visible and repeatable. A developer must still understand how requests travel through an application, how data is validated, how business rules are separated from transport concerns, how dependencies are managed, and how failures are observed and controlled.

This chapter uses a contemporary JavaScript/TypeScript-style backend as its principal illustration. The concepts also apply to frameworks such as Django, Laravel, Spring Boot, ASP.NET Core, and other platforms.

Learning Objectives

By the end of this chapter, you should be able to:

  • explain the purpose and responsibilities of a backend framework;
  • describe the request–response lifecycle;
  • configure routes, middleware, controllers, services, and templates where appropriate;
  • apply separation of concerns to backend application structure;
  • explain dependency injection and configuration management;
  • implement basic logging, validation, and error handling;
  • distinguish development, testing, and production concerns; and
  • evaluate common architectural choices and their limitations.

1. What a Backend Framework Provides

A framework normally supplies several related capabilities:

CapabilityPurpose
HTTP server integrationAccepts connections and produces responses
RoutingMaps an HTTP method and path to application behavior
Middleware pipelinePerforms shared processing before or after handlers
Controllers or handlersCoordinates a particular request or response
ServicesEncapsulates business rules and use cases
Data access supportConnects application logic to databases or external APIs
ConfigurationProvides environment-specific settings
LoggingRecords events useful for diagnosis and auditing
Error handlingConverts failures into safe, consistent responses
Testing toolsSupports unit, integration, and end-to-end tests

A framework may be opinionated or minimally prescriptive. An opinionated framework establishes conventions for directory structure, dependency injection, testing, and configuration. A minimalist framework gives developers more freedom but also leaves more architectural decisions to them. Neither style is universally superior: the appropriate choice depends on team experience, system complexity, operational needs, and the expected lifetime of the software.

Framework, library, and runtime

A runtime executes application code. Node.js, for example, provides a JavaScript runtime and system APIs. A library provides reusable functions that application code calls. A framework generally supplies a broader application structure and often calls application-defined components at predetermined points. This difference is sometimes described as inversion of control: the framework controls the overall lifecycle, while the developer supplies specific behavior.

2. The Request–Response Lifecycle

A typical backend request follows this sequence:

  1. A client resolves the server address and establishes a connection.
  2. The client sends an HTTP request containing a method, URL, headers, and possibly a body.
  3. The framework receives the request.
  4. Global middleware performs tasks such as request identification, parsing, security checks, or logging.
  5. The router selects a matching route.
  6. Route-specific middleware may authenticate or authorize the request.
  7. A controller extracts input and calls an application service.
  8. The service applies business rules and coordinates repositories or external systems.
  9. The controller converts the result into an HTTP response.
  10. Response middleware may add headers or record timing information.
  11. The client receives a status code, headers, and response body.

This sequence is not merely an implementation detail. It establishes boundaries. For example, authentication is usually a cross-cutting concern suitable for middleware, while calculating an order total belongs in a service or domain component rather than in a route declaration.

3. Routing

Routing maps an HTTP method and URL pattern to executable application behavior. Common methods include:

  • GET for retrieving a representation;
  • POST for creating a subordinate resource or initiating an operation;
  • PUT for replacing a resource;
  • PATCH for partially updating a resource; and
  • DELETE for removing a resource.

A route usually contains a path, a handler, and optionally a sequence of middleware functions. For example, an Express-style route might be written as follows:

router.get('/products/:id', authenticate, productController.getById);
router.post('/products', authenticate, validateProduct, productController.create);

The :id segment is a path parameter. A query such as /products?category=books contains a query parameter, while a JSON request body contains body data. These input locations have different meanings and should not be treated interchangeably.

Resource-oriented route design

Consistent route naming improves discoverability. A product collection may use:

OperationMethodPath
List productsGET/products
Retrieve one productGET/products/{id}
Create productPOST/products
Replace productPUT/products/{id}
Partially update productPATCH/products/{id}
Delete productDELETE/products/{id}

A route should describe the resource rather than expose an internal implementation detail. For example, /products is generally more stable than /runProductQuery.

Route ordering and specificity

Some routing systems evaluate routes in declaration order. A general route such as /products/:id placed before /products/search may incorrectly interpret search as an identifier. Developers should understand the matching rules of their framework and place more specific routes before general parameterized routes when required.

4. Middleware

Middleware is a function or component positioned in the request–response pipeline. It can inspect or modify the request, pass control onward, produce a response directly, or pass an error to a dedicated error handler.

Typical middleware includes:

  • request logging;
  • JSON body parsing;
  • CORS policy enforcement;
  • authentication;
  • authorization;
  • rate limiting;
  • input validation;
  • security headers;
  • correlation or request identifiers; and
  • centralized error handling.

A simplified Express-style middleware function is:

function requestId(req, res, next) {
  req.requestId = crypto.randomUUID();
  res.setHeader('X-Request-Id', req.requestId);
  next();
}

Calling next() transfers control to the next component. If middleware neither sends a response nor calls next(), the request may remain unresolved. Middleware should therefore have a clearly defined responsibility and completion behavior.

Middleware order

Order matters. A body parser must run before code that reads the parsed body. Authentication must normally occur before authorization. Error middleware must be registered according to the framework’s error-processing rules. A useful sequence is:

  1. security and request identification;
  2. request logging;
  3. body and cookie parsing;
  4. general rate limiting;
  5. route selection;
  6. authentication;
  7. authorization;
  8. validation;
  9. controller execution; and
  10. centralized error handling.

The exact order varies by framework and application, but the principle is general: middleware dependencies must be explicit and tested.

5. Controllers, Services, and Repositories

A maintainable backend separates transport concerns from business concerns.

Controllers

A controller receives a framework request, obtains the relevant input, invokes an application operation, and constructs the response. It should not contain extensive business rules or database query logic.

export async function create(req, res, next) {
  try {
    const product = await productService.create(req.body);
    res.status(201).json(product);
  } catch (error) {
    next(error);
  }
}

Services

A service expresses a use case or business operation. It may validate business conditions, coordinate multiple repositories, and apply transactions. It should ideally be usable without an HTTP request, which makes it easier to test and reuse.

export async function create(input) {
  const existing = await productRepository.findBySku(input.sku);
  if (existing) {
    throw new ConflictError('SKU already exists');
  }
  return productRepository.insert(input);
}

Repositories and data-access components

A repository hides persistence details from the service. The service asks for a product; it does not need to know whether the product is stored in PostgreSQL, a document database, or an external API. This abstraction should be meaningful rather than excessive. Creating a repository interface for every trivial function can add complexity without improving design.

Separation of concerns

Separation of concerns means that each component has a focused reason to change. A controller may change because the HTTP API changes; a repository may change because the storage technology changes; a service may change because a business rule changes. If all three concerns occupy one large route function, every change becomes riskier.

A common structure is:

src/
  config/
  middleware/
  routes/
  controllers/
  services/
  repositories/
  models/
  views/
  errors/
  app.js
  server.js

The exact names are less important than the boundaries. A small application may combine some directories, while a larger application may organize code by feature:

src/modules/products/
  product.routes.js
  product.controller.js
  product.service.js
  product.repository.js

Feature-based organization can reduce navigation costs because related files remain together.

6. Dependency Injection

Dependency injection (DI) means that a component receives the objects it depends on rather than constructing them internally. Consider the difference:

class ProductService {
  constructor(productRepository, logger) {
    this.productRepository = productRepository;
    this.logger = logger;
  }
}

The service does not create a database client or choose a global logger. A composition root creates the concrete objects and supplies them:

const repository = new ProductRepository(database);
const service = new ProductService(repository, logger);

DI provides several benefits:

  • dependencies are visible in the constructor;
  • unit tests can supply fakes or mocks;
  • infrastructure can be replaced with less application change; and
  • object lifetimes can be controlled centrally.

Some frameworks provide a DI container that resolves dependencies automatically. A container may manage singleton, request-scoped, or transient lifetimes. Developers must understand those lifetimes. A singleton that stores request-specific data can cause data leakage between users, while creating expensive resources repeatedly can harm performance.

DI is not a goal in itself. It is valuable when it improves replaceability, testing, and clarity. Excessive abstraction can obscure simple programs.

7. Configuration Management

Configuration includes values that vary by environment or deployment, such as database URLs, port numbers, feature flags, and external service endpoints. Configuration should be separated from application code and should be validated at startup.

const config = {
  port: Number(process.env.PORT || 3000),
  databaseUrl: process.env.DATABASE_URL,
  logLevel: process.env.LOG_LEVEL || 'info'
};

if (!config.databaseUrl) {
  throw new Error('DATABASE_URL is required');
}

Sensitive values such as passwords, signing keys, and API tokens should not be committed to source control. Production secrets should be supplied through an appropriate secret-management mechanism. Environment variables are useful, but they are not automatically secure; access permissions and deployment practices remain important.

Configuration should be typed or schema-validated where possible. A missing variable should produce a clear startup failure rather than a confusing database error several minutes later.

8. Templates and API Responses

Backend applications may return JSON, HTML, files, redirects, or status-only responses. A server-rendered application uses a template engine to combine a view template with data:

return res.render('products/show', { product });

Templates should primarily present data. Business rules should remain in services or domain components. Escaping output is essential when rendering user-controlled data, because unescaped content can enable cross-site scripting (XSS). Framework defaults should be understood rather than blindly trusted.

For JSON APIs, response contracts should be stable and documented. A successful response might contain a resource representation, while errors should use a consistent structure such as:

{
  "error": {
    "code": "PRODUCT_NOT_FOUND",
    "message": "The requested product does not exist",
    "requestId": "..."
  }
}

Internal stack traces and database details should not be exposed to clients.

9. Validation, Errors, and Security

Validation occurs at more than one level. Syntactic validation checks whether a value has the correct shape and type. Business validation checks whether the operation is permitted under application rules. Database constraints provide a final integrity boundary.

For example, a product request may require a non-empty name and a non-negative price. Even if the API validates the price, the database should also enforce an appropriate constraint where feasible.

Error categories commonly include:

  • client input errors, often represented by HTTP 400;
  • authentication failures, commonly 401;
  • authorization failures, commonly 403;
  • missing resources, commonly 404;
  • conflicts, commonly 409; and
  • unexpected server failures, commonly 500.

These status codes are conventions rather than substitutes for a documented API contract. Error messages should be useful but should not disclose credentials, SQL statements, stack traces, or personal data.

Security practices include parameterized database queries, password hashing with an appropriate password-hashing algorithm, secure cookie settings, transport encryption, least-privilege access, dependency updates, and careful handling of logs. Authentication establishes identity; authorization determines what that identity may do. Confusing these concepts creates security defects.

10. Logging and Observability

Logging records events, while observability combines logs with metrics and traces to help explain system behavior. A production log should usually be structured, searchable, and associated with a request identifier.

Useful fields may include timestamp, severity, event name, request ID, route, status code, duration, and service name. Sensitive values should be excluded or redacted. Logging every request body may create privacy and storage risks.

A simple performance measure is request duration:

request duration = response time − request start time

This is a definition, not a universal performance formula. The timestamps must use the same clock basis and unit, such as milliseconds. Percentiles such as p95 can be more informative than an average because a small number of slow requests may be operationally significant.

Logs should support diagnosis, not become a substitute for proper error handling. A log level policy commonly distinguishes debug, info, warning, and error events.

11. Testing and Application Environments

A backend commonly has separate development, test, staging, and production environments. Separation reduces the risk that experiments or tests alter real customer data.

Testing layers include:

  • Unit tests, which test a small component in isolation;
  • Integration tests, which test interactions with a database or external component; and
  • End-to-end tests, which exercise the application through a realistic interface.

Dependency injection makes unit testing easier because a service can receive an in-memory repository or test double. Integration tests remain necessary because mocks cannot prove that a real database query, transaction, or serialization format works correctly.

Application startup should be separated from application construction where practical. An app module can create and configure the application, while a server module opens the network port. This arrangement makes tests able to import the application without starting a real server.

12. Illustrative Case: A Library Borrowing API

Background

A small public library wants an API that allows registered members to borrow and return books. The first prototype places database queries, authentication checks, and borrowing rules directly in route handlers.

Problem

The prototype permits a member to borrow a book even when the member has reached the borrowing limit. Similar logic is duplicated in the mobile and web endpoints. Tests are difficult because route handlers create database connections directly.

Analysis

The core rule is a business rule: a member may borrow a book only when the book is available and the member has fewer than the permitted number of active loans. Authentication middleware can identify the member, but it should not decide whether the borrowing limit has been exceeded. That decision belongs in a service.

Approach

A revised structure is:

routes -> controllers -> borrowing service -> repositories

The route declares the endpoint:

router.post('/loans', authenticate, borrowController.create);

The controller translates HTTP input:

async function create(req, res, next) {
  try {
    const loan = await borrowingService.borrow({
      memberId: req.user.id,
      bookId: req.body.bookId
    });
    res.status(201).json(loan);
  } catch (error) {
    next(error);
  }
}

The service applies the rule:

async function borrow({ memberId, bookId }) {
  const member = await memberRepository.findById(memberId);
  const book = await bookRepository.findAvailableById(bookId);
  const activeLoans = await loanRepository.countActiveByMember(memberId);

  if (!member) throw new NotFoundError('Member not found');
  if (!book) throw new ConflictError('Book is unavailable');
  if (activeLoans >= member.borrowingLimit) {
    throw new ConflictError('Borrowing limit reached');
  }

  return loanRepository.create({ memberId, bookId });
}

In a production system, the availability check and loan creation should be protected against race conditions, usually through a transaction and suitable database constraints. Otherwise, two simultaneous requests might both observe an available book and create conflicting loans.

Outcome

The web and mobile endpoints can reuse the same service. Controllers remain small, tests can supply fake repositories, and errors are handled consistently. The design does not guarantee correctness by itself: transaction behavior, authorization, database constraints, and operational monitoring still require testing.

Learning points

  1. Authentication and business authorization are different concerns.
  2. Services should express use cases independently of HTTP.
  3. Repositories isolate persistence details but do not remove the need for database integrity.
  4. Concurrency must be considered when a rule depends on current state.
  5. Architectural structure is useful only when supported by tests and clear contracts.

13. Contemporary Practices and Limitations

Modern frameworks increasingly support TypeScript, automatic API documentation, schema validation, asynchronous processing, containerized deployment, and observability integrations. Frameworks may also provide adapters for serverless functions and edge runtimes.

AI-assisted coding can generate route handlers, tests, and configuration examples. However, generated code must be reviewed for insecure defaults, incorrect authorization, dependency vulnerabilities, privacy risks, and incompatibility with the project’s framework version. AI tools should assist engineering judgment rather than replace code review and testing.

Frameworks also have limitations. Abstractions can hide database costs, middleware can become difficult to trace, automatic dependency injection can obscure object lifetimes, and large frameworks may encourage unnecessary complexity. A framework’s conventions should be learned, documented, and challenged when they conflict with system requirements.

Best Practices Checklist

  • Keep controllers thin and services focused.
  • Validate external input at the application boundary.
  • Use parameterized queries and least-privilege credentials.
  • Centralize error translation and avoid leaking internals.
  • Validate configuration during startup.
  • Use structured logs with request identifiers.
  • Separate application construction from server startup.
  • Test business rules independently of HTTP.
  • Use database constraints and transactions where integrity requires them.
  • Document route contracts, status codes, and authentication requirements.
  • Keep dependencies updated and review security advisories.
  • Choose abstractions that reduce complexity rather than merely increasing file count.

Chapter Summary

Backend frameworks provide reusable mechanisms for HTTP handling, routing, middleware, configuration, logging, errors, and testing. The request–response lifecycle explains how these mechanisms cooperate. Routing maps requests to behavior, middleware handles cross-cutting concerns, controllers coordinate transport-level work, services express business use cases, and repositories isolate persistence.

Dependency injection improves testability and replaceability by supplying dependencies from outside a component. Configuration should be environment-specific, validated at startup, and protected when sensitive. Templates and JSON responses must use stable contracts and safe output handling. Validation, centralized error handling, structured logging, testing, and observability are essential parts of professional backend development.

The central architectural principle is separation of concerns. Good structure does not mean following one universal directory layout; it means creating clear, testable boundaries whose responsibilities are understandable and maintainable.

Key Terms

Backend framework: A platform providing structure and reusable tools for server-side applications.

Runtime: Software that executes application code.

Routing: Mapping HTTP methods and paths to application behavior.

Middleware: A component that processes a request or response within a pipeline.

Controller: A component that coordinates HTTP input and output.

Service: A component that implements a business operation or use case.

Repository: A component that abstracts persistence or data access.

Dependency injection: Supplying a component’s dependencies from outside the component.

Inversion of control: A design in which a framework controls execution flow and invokes application code.

Configuration: Environment-specific settings used by an application.

Authentication: Establishing the identity of a user or system.

Authorization: Determining what an authenticated identity is allowed to do.

Validation: Checking whether input satisfies structural or business requirements.

Request identifier: A value used to correlate logs and events for one request.

Observability: The ability to understand system behavior through logs, metrics, and traces.

Unit test: A test of a small component in isolation.

Integration test: A test of interactions between components or infrastructure.

Separation of concerns: Organizing software so each component has a focused responsibility.

Transaction: A controlled unit of database work intended to preserve consistency.

Composition root: The place where application dependencies are constructed and connected.

Review Questions

Short Answer Questions

  1. What is the primary purpose of a backend framework?
  2. Distinguish authentication from authorization.
  3. What responsibility should normally belong to a controller?
  4. Why should configuration be validated during application startup?
  5. What is the purpose of a request identifier in logging?

Long Answer Questions

  1. Explain the request–response lifecycle and identify where routing, middleware, controllers, and services participate.
  2. Discuss how separation of concerns improves maintainability in a backend application.
  3. Explain dependency injection and evaluate its benefits for testing and infrastructure replacement.
  4. Compare unit, integration, and end-to-end testing in the context of a backend API.
  5. Describe a secure and maintainable approach to validation, error handling, and logging.

Analytical / Application Questions

  1. A route handler contains authentication, database queries, price calculations, and response formatting. Analyze the design and propose a more suitable structure.
  2. Two users submit requests to reserve the last available item at exactly the same time. Explain the possible failure and recommend application and database controls.
  3. Design the route, middleware, controller, service, and repository responsibilities for an online course-enrollment endpoint. Justify where authentication, authorization, validation, and capacity checks should occur.

Multiple Choice Questions with Answers

Question 1

Which component is primarily responsible for mapping an HTTP method and path to application behavior?

A. Router

B. Repository

C. Logger

D. Template engine

Correct Answer: A. Router

Question 2

What is the main architectural purpose of middleware?

A. To replace the database

B. To process shared concerns within the request–response pipeline

C. To define all business rules

D. To compile frontend CSS

Correct Answer: B. To process shared concerns within the request–response pipeline

Question 3

Which responsibility best belongs in a service?

A. Parsing the HTTP method

B. Adding a response header only

C. Applying a business rule for a use case

D. Opening a browser window

Correct Answer: C. Applying a business rule for a use case

Question 4

What does dependency injection require a component to do?

A. Construct every dependency internally

B. Receive dependencies from an external composition mechanism

C. Avoid all testing

D. Store passwords in source code

Correct Answer: B. Receive dependencies from an external composition mechanism

Question 5

Which status code conventionally indicates that a requested resource was not found?

A. 201

B. 301

C. 404

D. 503

Correct Answer: C. 404

Question 6

Why should sensitive secrets not be committed to source control?

A. They make routing slower

B. They may be exposed to unauthorized users

C. They prevent JSON parsing

D. They disable middleware

Correct Answer: B. They may be exposed to unauthorized users

Question 7

Which testing level is most focused on a small component in isolation?

A. Unit testing

B. End-to-end testing

C. Load balancing

D. Deployment testing

Correct Answer: A. Unit testing

Question 8

What is the principal difference between authentication and authorization?

A. Authentication identifies an actor; authorization determines permitted actions

B. Authentication is for databases; authorization is for logs

C. Authentication formats JSON; authorization formats HTML

D. There is no difference

Correct Answer: A. Authentication identifies an actor; authorization determines permitted actions

Question 9

Which practice helps prevent two simultaneous requests from creating conflicting database results?

A. Removing all validation

B. Using transactions and suitable database constraints

C. Logging only successful requests

D. Putting all code in one controller

Correct Answer: B. Using transactions and suitable database constraints

Question 10

Why should controllers generally remain thin?

A. To keep business rules and persistence concerns in focused, testable components

B. To prevent the server from accepting GET requests

C. To eliminate the need for configuration

D. To ensure every response is HTML

Correct Answer: A. To keep business rules and persistence concerns in focused, testable components

Backend Development
Chapter 4 of 14