# Starlette: the ASGI toolkit behind FastAPI, and when to use it directly

> Starlette is a lightweight ASGI framework and toolkit for async Python web services. Its real value is that every piece works on its own, which is also why it is not a batteries-included web framework.

**Kludex/starlette** — The little ASGI framework that shines. 🌟

- Repository: https://github.com/Kludex/starlette
- Website: https://starlette.dev
- Stars: 12,639 · Forks: 1,337
- Language: Python
- License: BSD-3-Clause
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/kludex-starlette

## What Starlette solves, and who ends up using it

Starlette is a lightweight ASGI framework and toolkit, described in its README as "ideal for building async web services in Python". The problem it addresses is narrow but common: you want an async HTTP service in Python without pulling in a full web framework, and you want the pieces to be usable separately. The README states the project is designed to be used "either as a complete framework, or as an ASGI toolkit", and that any of its components can be used independently.

That second claim is the one that matters most in practice. The repository's own example of toolkit usage imports a single response class and writes a bare ASGI callable with no router at all. If you only need a streaming response object inside an application whose routing you already own, Starlette is meant to be importable at that granularity.

The audience is therefore split. Application developers who want routing, middleware and WebSocket handling in one small package use it as a framework. Library authors building something that must run on any ASGI server use it as a toolkit, because the components do not drag a framework along with them. The README's modularity section makes this explicit: the design is intended to let reusable components be shared between any ASGI framework.

## Routing, responses and the ASGI callable at the centre

The mechanism is the ASGI interface itself. An application in Starlette is a callable taking scope, receive and send. The README's toolkit example asserts the scope type is http, constructs a PlainTextResponse, and awaits it with those same three arguments. Everything else in the framework is built on that shape.

The full-framework path wraps it in a Starlette application object that takes a routes list. Each entry is a Route pairing a path with an endpoint, and the endpoint is an async function receiving a request and returning a response. The README's main example defines a homepage endpoint returning JSONResponse with a single key, and passes debug=True plus the routes list to the constructor.

Around that core sit the features the README enumerates: WebSocket support, in-process background tasks, startup and shutdown events, CORS, GZip, static files, streaming responses, and session and cookie support. Middleware in Starlette wraps the ASGI callable, which is why the same middleware can be reused outside Starlette entirely. The project also ships a test client built on httpx2, which is a separate optional dependency rather than something bundled in.

Two claims in the README are worth reading carefully. The project states 100% test coverage and a 100% type annotated codebase. Neither is a statement about your application's correctness, and neither substitutes for integration testing against a real server. They describe the repository's own standards.

## Installing Starlette and serving a first route

Installation is a single package, and the README pairs it with an ASGI server because Starlette does not serve itself. The two commands below are the ones the README gives, first for the framework and then for uvicorn.

```bash
pip install starlette
pip install uvicorn
```

With both installed, the README's example file defines an application object and one route. Note that the endpoint is async and returns a JSONResponse directly; there is no return-value serialisation step.

```python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route


async def homepage(request):
    return JSONResponse({'hello': 'world'})

routes = [
    Route("/", endpoint=homepage)
]

app = Starlette(debug=True, routes=routes)
```

Save that as main.py and run it with uvicorn, passing the module and the application attribute separated by a colon.

```bash
uvicorn main:app
```

The README shows the server logging a start line and then a line reading that Uvicorn is running on http://127.0.0.1:8000. Requesting the root path should return the JSON body from the endpoint. The README also notes that adding --reload to the uvicorn command enables auto-reloading on code changes, which is the flag you want during development and not in production.

If you plan to use the optional features, the README lists them individually and also provides a single extras install. The extras install is the one to prefer when you want forms, sessions, templates and the test client together.

```bash
pip install starlette[full]
```

## Optional dependencies are where the sharp edges are

Starlette's only required dependency is anyio, pinned in pyproject.toml as anyio>=4.0.0,<5, with typing_extensions added for Python versions below 3.13. Everything else is opt-in, and the README is explicit about which package unlocks which feature: httpx2 for TestClient, jinja2 for Jinja2Templates, opentelemetry-api for OpenTelemetryMiddleware, python-multipart for form parsing through request.form(), itsdangerous for SessionMiddleware, and pyyaml for SchemaGenerator.

The failure mode here is a runtime one rather than an install-time one. A missing optional dependency does not stop your application from starting if you never touch the feature, and then it surfaces the first time a request hits the code path that needs it. Form parsing is the clearest case: request.form() depends on python-multipart, which pyproject.toml requires at version 0.0.18 or newer. If your deployment environment is built from a lockfile that omitted the extras, you will discover this in production rather than locally.

The second constraint is the Python floor. pyproject.toml sets requires-python to >=3.10 and lists classifiers through Python 3.15. If you are maintaining a service on an older interpreter, Starlette is not an option at all, and no amount of pinning changes that.

The third is subtler. Because Starlette is designed for reuse across ASGI frameworks, its middleware and response components are deliberately generic. There is no application-level convention for where configuration lives, how dependencies are injected, or how the project directory is laid out. That is a design choice, not an oversight, but it means two Starlette codebases can look almost nothing alike. Teams that want one obvious way to structure a service will find the freedom expensive.

## Starlette against FastAPI: same foundation, different layer

The most common comparison is with FastAPI, and the repository itself gives you the terms for it. Starlette describes itself as a framework or toolkit; FastAPI is not discussed in the README at all, so the honest framing is that they operate at different levels of the same stack.

Starlette gives you routing, responses, middleware hooks, WebSocket handling and startup and shutdown events. What it does not give you, judging by the README's feature list, is request-body validation against type hints, automatic OpenAPI schema generation from your function signatures, or a dependency injection system. The README does mention SchemaGenerator as a component requiring pyyaml, but it is listed among optional features rather than presented as an application-wide contract.

So the practical difference is where the conventions live. With Starlette, you write the validation and the schema yourself, or you add a library that does it. With a framework built on top of Starlette, those conventions arrive pre-decided. Choosing Starlette directly is choosing to own those decisions, and the modularity section of the README is essentially an argument that this is a feature: components built against Starlette can be shared between any ASGI framework.

There is a real cost to that position. When something is wrong with validation or schema output in a higher-level framework, the bug may live in the layer above Starlette, and you are dependent on that layer to fix it. When you build on Starlette directly, every such bug is yours to fix.

## Maintenance, licensing and what an upgrade actually costs

The repository is not archived, and the last push was on 2026-09-20. Recent releases are close together: 1.5.0 on 2026-08-08, 1.5.1 later the same day, and 1.6.0 on 2026-08-08 as well. That clustering is worth noting if you pin versions, because three releases in one day means the changelog, not the version number, is the thing to read before upgrading.

One detail in pyproject.toml deserves attention: the classifier list includes "Development Status :: 3 - Alpha" while the README calls the project production-ready. Those two statements come from the same repository and they point in opposite directions. Treat the classifier as a statement about API stability expectations rather than about whether the code works, and budget for reading release notes accordingly.

The dependency surface keeps upgrade cost low. anyio is bounded below 5, and typing_extensions is conditional on the Python version. The optional extras are mostly unbounded, which means pip can move them under you: jinja2, itsdangerous, pyyaml and opentelemetry-api carry no version constraints in the full extra, while python-multipart, httpx and httpx2 do. If you install starlette[full], the unpinned half of that list is where a surprise upgrade can originate.

Licensing is BSD-3-Clause, declared in pyproject.toml with license-files pointing at LICENSE.md. That is a permissive licence, and the repository does not add terms beyond it. This is not legal advice; check the licence text and your own distribution obligations.

## Conclusion

Adopt Starlette directly if you want to assemble routing, middleware and responses yourself and you accept that forms, sessions and templates need optional packages. Do not pick it if you expect an opinionated project layout, a dependency injection system or an ORM, because the README lists none of those. Before committing, verify that your ASGI server of choice is installed alongside it, that anyio 4.x is acceptable in your dependency tree, and that every optional extra you rely on (python-multipart for request.form(), itsdangerous for SessionMiddleware, jinja2 for Jinja2Templates) is actually present in your environment.

## FAQ

### What is Starlette used for?

It is used to build async web services in Python, either as a complete framework with routing and middleware or as a toolkit whose components can be used independently. The README lists HTTP handling, WebSocket support, background tasks, startup and shutdown events, and middleware such as CORS and GZip.

### How do you install Starlette?

Install the package with pip install starlette, then install an ASGI server such as uvicorn, which the README gives as pip install uvicorn. For the optional features you can use pip install starlette[full].

### What is Starlette vs FastAPI?

Starlette is the lower layer: a lightweight ASGI framework and toolkit providing routing, responses, middleware and WebSocket handling. The Starlette README does not discuss FastAPI, so the difference is best understood as Starlette supplying the ASGI foundation while validation and schema conventions live in whatever is built on top of it.

### Is Starlette written in Python?

Yes. The repository's primary language is Python, pyproject.toml sets requires-python to >=3.10, and the README states the codebase is 100% type annotated.

### What is Starlette and uvicorn?

Starlette is the application framework and uvicorn is an ASGI server that runs it. The README's installation section instructs you to install an ASGI server such as uvicorn alongside Starlette, and its example runs the application with uvicorn main:app.

### What is a Starlette app?

In the README's example, it is an instance of the Starlette application class constructed with debug=True and a routes list, where each route pairs a path with an async endpoint function that returns a response. That object is what you pass to an ASGI server such as uvicorn.

## Sources

- [Kludex/starlette on GitHub](https://github.com/Kludex/starlette)
- [License: BSD-3-Clause](https://github.com/Kludex/starlette/blob/main/LICENSE)
- [Project website](https://starlette.dev)
- [README](https://github.com/Kludex/starlette/blob/main/README.md)
- [Releases](https://github.com/Kludex/starlette/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kludex-starlette
