Inside a backend server
The request lifecycle, middleware, statelessness, configuration, secrets and logging.
12 min read
You will learn
- Describe the lifecycle of a request inside a server
- Explain the middleware (pipeline) pattern
- Explain why servers should be stateless and where state goes instead
- Handle configuration, secrets and logging properly
What happens inside the server
When a request reaches your server, it goes through the same stages in nearly every framework — ASP.NET Core, Express, Django, Spring:
- Accept the connection and parse the HTTP request.
- Run it through a pipeline of middleware: logging, error handling, HTTPS redirection, authentication, CORS, compression…
- Route it: match method + path to a handler (
GET /orders/{id}→GetOrder). - Bind inputs: path values, query string, headers, JSON body → typed parameters, then validate them.
- Run your business logic: talk to the database, caches, other services.
- Serialize the result (usually JSON) and write the response, which travels back through the pipeline.
The middleware pattern
Each middleware is a small component that receives the request, can do something, then decides whether to call the next one:
// Conceptually, every middleware looks like this:
async Task Middleware(HttpContext context, Func<Task> next)
{
// ... before: inspect the request, maybe reject it early ...
await next(); // pass control down the pipeline
// ... after: inspect or modify the response ...
}This makes cross-cutting concerns composable, and order matters: exception handling must wrap everything, authentication must run before authorization, static files should short-circuit before expensive work. Follow different requests through a real ASP.NET Core pipeline:
ASP.NET Core middleware pipeline
Follow a request in through each middleware and the response back out
GET https://shop.example/api/orders Authorization: Bearer eyJhbGci…
- 1Exception handler↓ request
- 2HTTPS redirection
- 3Static files
- 4Routing
- 5Authentication
- 6Authorization
- 7Endpoint
Program.cs
var app = builder.Build();app.UseExceptionHandler("/error");app.UseHttpsRedirection();app.UseStaticFiles();app.UseRouting();app.UseAuthentication();app.UseAuthorization();app.MapGet("/api/orders", GetOrders).RequireAuthorization();app.Run();
Middleware runs in the order it's registered. Each one can act, then call next() — or stop the request right there.
try/catch.Stateless servers
In production you run several instances of your app behind a load balancer, and they come and go (deployments, autoscaling, crashes). So a server must not keep anything important only in its own memory:
| State | Where it should live |
|---|---|
| Business data | Database |
| Logged-in user | A signed cookie/token, or a session in Redis/database |
| Uploaded files | Object storage (S3, Azure Blob), not the server's disk |
| Cached data | Shared cache (Redis) — or a local cache that's safe to lose |
| Background work | A queue, not a fire-and-forget task |
A stateless server can be restarted or replaced at any moment without losing anything — the foundation of scaling (later in this track).
Configuration
The same build should run in development, testing and production with different settings. Keep configuration outside the code, layered by precedence:
appsettings.json ← defaults, committed
appsettings.Production.json ← per-environment overrides
environment variables ← set by the hosting platform
command-line arguments{
"ConnectionStrings": { "Shop": "Host=localhost;Database=shop" },
"Payment": { "TimeoutSeconds": 10 }
}An environment variable Payment__TimeoutSeconds=30 overrides the file (double underscore = nesting).
Secrets
Connection strings with passwords, API keys and signing keys are secrets. They must never be committed to source control.
- Development:
dotnet user-secrets set "Stripe:ApiKey" "sk_test_…"(stored outside the project folder). - Production: a secrets manager — Azure Key Vault, AWS Secrets Manager, HashiCorp Vault — or environment variables injected by the platform.
- Rotate secrets regularly, and immediately if one leaks.
Logging
When something goes wrong in production, logs are often all you have. Good logs are:
- Leveled —
Trace,Debug,Information,Warning,Error,Critical; production usually recordsInformationand above. - Structured — values are kept as named fields, not just baked into text:
logger.LogInformation("Order {OrderId} placed by {CustomerId} for {Total}", order.Id, customer.Id, order.Total);Log platforms (Seq, Elastic, Application Insights, Grafana Loki) can then answer "show all errors for OrderId 42".
- Correlated — a request/trace id ties together everything one request did, across services.
- Safe — never log passwords, tokens or full credit card numbers.
Key takeaways
- Requests flow through a pipeline: middleware → routing → binding/validation → logic → serialization.
- Middleware can act before and after
next(), or short-circuit; order matters. - Keep servers stateless; put state in databases, caches, object storage and queues.
- Externalize configuration, keep secrets out of git, and log in a structured, leveled way.
Quiz
4 questions · 3 correct to pass1.Why should web servers be stateless?
2.In a middleware pipeline, what can a middleware do?
3.Where should a production database password live?
4.What's the advantage of structured logging?
To complete this lesson
- ○ Pass the quiz (3/4 correct)