A smaller, faster and more inspectable REST service is the typical result when you compose a framework-free stack, because you only include the pieces you actually need: an ASGI server, a router, request and response handling, validation and serialization, auth middleware, and observability. Start with a locked runtime and an ASGI server such as Uvicorn, then mount a standalone router, wire typed models for validation, and expose an OpenAPI document from your explicit routes. For development use Uvicorn with the --reload flag and for production run the server under a process manager such as Gunicorn with the Uvicorn worker class, or consider a Rust-based ASGI server where throughput matters. Follow the eight-step workflow below and keep the environment reproducible by creating a project folder and running python -m venv .venv as your first concrete action.
1. Project scaffolding and lock the runtime
Decide the Python runtime and create an isolated environment now. Use Python 3.10 or newer and a dedicated project folder to prevent dependency leakage. Concrete commands to get started are simple: create the directory, run Python -m venv .venv, then activate it with Source .venv/bin/activate on macOS and Linux or .venv\Scripts\Activate.ps1 on Windows.
Worked example, file layout: imagine a microservice called Orders. Create Orders/, initialize the venv, then commit a Requirements.txt or a lock file so CI and production run the identical runtime. That single action establishes a reproducible base for installing an ASGI server, a router, and validation libraries.
2. Pick the ASGI server and process model
Choose the server early; it sets the concurrency model, HTTP features and deployment commands. In 2026 Uvicorn remains the de facto ASGI server; it's built on Uvloop and Httptools and supports HTTP/1.1, HTTP/2 and WebSockets. For local development run Uvicorn directly with the --reload flag. For production run the server under a process manager such as Gunicorn using the Uvicorn worker class to get fault tolerance and predictable process behaviour.
Worked example, performance choice: if your team needs maximum throughput under high load, evaluate a Rust-based server with native ASGI support. Several drop-in Rust servers benchmark faster than Uvicorn in heavy scenarios and can be slotted into the same deployment pattern as Uvicorn while keeping the ASGI callable contract intact.
3. Route requests with a standalone router or ASGI primitives
Import a router component to retain URL dispatch, parameter parsing and mount points while avoiding a large dependency. Many projects expose a Router class that you can import independently to register path handlers and to mount sub-routers. Use that where you want concise path registration and parameter parsing without a large dependency.
Worked example, handler signatures: you can either implement handlers as minimal ASGI callables that accept the Scope, receive, send signature, or use lightweight Request and Response primitives from a minimal ASGI set of tools that exposes those types for composition. For an /orders POST, register the path on a router and have the handler expect a typed model, explained in the next step.
4. Validation and serialization with typed models
Model incoming JSON and wire validation explicitly when you forego a framework's guardrails, so choose a schema library that performs type-checked validation and JSON serialization. Typed data models provide a safety net: they validate fields and types, normalise nested objects and produce consistent responses. Pick a library that integrates with async code and fits your preference for API of models versus decorators.
Worked example, error handling: design clear error responses and reusable validators so your endpoint logic assumes well-formed input. When a model validation fails return a consistent JSON error with status code and an errors field that client libraries can parse programmatically.
5. Authentication, authorization and middleware
Centralise authentication in middleware so route code stays simple and policy enforcement is consistent. Put an ASGI middleware layer in place that decodes and validates tokens and injects the authenticated principal into request.state. JSON Web Tokens are the commonly recommended token format for stateless APIs; pair JWT authentication with object-level authorization checks in handlers so users can't act on resources they don't own.
Worked example, middleware chain: middleware should perform token decoding, signature verification and expiry checks, then attach a Request.state.user or similar principal. Downstream handlers perform object-level checks against that principal. For stricter services also include rate limiting and policy checks inside the middleware chain so enforcement is both central and consistent.
6. Documentation and schema publication
Publish a machine-readable contract so your API is discoverable by humans and tooling. Export an OpenAPI document from your explicit route and model definitions and publish it alongside the service. If you operate GraphQL services, produce and publish a schema accordingly. Frameworks normally automate this, but library-assisted or manual schema generation is straightforward once routing and models are explicit.
Worked example, interactive docs: by exporting OpenAPI you enable clients to generate SDKs and host interactive documentation. Keep your models and route metadata in one place so the published schema reflects the actual behaviour of endpoints rather than stale hand-written docs.
7. Testing, observability and packaging for deployment
Exercise handlers through the ASGI interface so tests run fast and do not require a server process. Write automated tests that call handlers via the ASGI contract. Add monitoring and metrics export to track latency, error rates and resource use; standard exporters in 2026 integrate with Prometheus-style collectors.
Worked example, CI pipeline: write tests that create an ASGI scope for an incoming HTTP request, call your app callable and assert the response body and status. Export Prometheus-compatible metrics and configure your container to expose a metrics endpoint. Containerise the service, include health-check endpoints, and use the same start command in CI, staging and production to avoid drift between environments.
8. Deploy with a process manager and CI/CD
Run the ASGI server under a process manager to gain fault tolerance and consistent start-up behaviour; in production pair the server with a process manager like Gunicorn using the Uvicorn worker class, or run the Rust-based ASGI server under the same process supervision model. Use containers to standardise builds and releases and run the identical image across environments.
Worked example, deployment checklist: create a container image that installs dependencies, exposes health and metrics endpoints, and uses the production start command as the container entrypoint. Configure CI to build that image, run the test suite with the same start command in a lightweight environment and push the image to your registry for staging and production.
Two implementation patterns and trade-offs
Choosing a pattern balances developer ergonomics against control.
The pragmatic composition pattern uses specialist libraries: an ASGI server for runtime, a standalone router for dispatch, typed models for validation and serialization, middleware for auth and rate limits, and libraries for metrics and OpenAPI generation. That yields concise code and predictable performance.
Worked example, pragmatic stack: Uvicorn as server, a standalone Router for paths, typed models for schema, JWT middleware for authentication, and a Prometheus-compatible exporter for metrics. This keeps dependencies small, while still giving you the machinery you need for production.
The minimalist educational pattern implements HTTP parsing, socket handling and concurrency primitives directly in Python. That route is excellent for learning or extreme customisation, but it pushes security, correctness and performance responsibilities back onto the developer. One source in the literature demonstrated this hands-on value by building everything from sockets and HTTP parsing as a learning exercise, while another recommended starting with FastAPI and Strawberry where teams prefer integrated stacks and developer convenience.
When not to go framework-free
The wrong decision costs time. Full frameworks remain the faster route when you need feature-rich APIs because they provide routing, validation, docs, dependency injection and background tasks out of the box. Choose a framework-free stack when you want minimal overhead, tight control over components, or are building a microservice with a narrow responsibility. If you need admin dashboards, ORMs or heavy ecosystem integrations quickly, adopt a framework instead.
Worked example, pick a priority: if your project deadline requires an admin dashboard and a mature ORM integration the framework path reduces delivery risk. If the priority is low memory footprint and inspection, a composed stack is preferable.
In Short
• Use Python 3.10+ and create an isolated venv with Python -m venv .venv.
• For development run Uvicorn with --reload; in production run the server under a process manager such as Gunicorn using the Uvicorn worker class or use a Rust-based ASGI server for high throughput.
• Import a standalone Router or implement handlers with the ASGI signature Scope, receive, send; use typed models for validation and JSON (de)serialization.
• Implement JWT auth as ASGI middleware, export OpenAPI for discoverability and instrument metrics for Prometheus-style collectors before containerising and deploying the same image across environments.
Related Articles
- Cut your water bill: claim WaterSure or your supplier's help
- 5 Tips Freshers Need Before University Starts
- Cryptographic receipts: proof vs speed in 9 steps
If you follow the practical eight-step workflow above you will end up with a production-quality, framework-free REST service whose behaviour is explicit and auditable. Start with the concrete action that sources converge on: create your project directory and run python -m venv .venv to lock the runtime and prevent dependency drift.
This article was created with AI assistance.