Open-source project
dart-lang/shelf avatar
dart-lang/shelf

Shelf is a Dart middleware model where a handler is one function

Web server middleware for Dart

1,011 stars139 forksDartBSD-3-Clause

At a glance

What is it?
Shelf gives Dart a small set of types and a single function shape for web server middleware, so a handler takes a request and returns a response, and the same model covers strings and byte streams, synchronous and asynchronous code. The repository is a monorepo of nine packages, and the one that enforces HTTP compliance is deliberately not published.
Who is it for?
Adopt Shelf when you are writing a Dart HTTP service and want a composable middleware model with routers, static files, proxies and web sockets that share one handler contract. Do not adopt it expecting a complete server or a full framework, because the repository contains no server package and the router and static file support live in separate published packages.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Dart, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One function shape for the whole request pipeline

Shelf makes it easy to create and compose web servers and parts of web servers, and the way it does that is by exposing a small set of simple types and mapping server logic into a single function. The request is the argument, the response is the return value.

That is the entire contract, and almost everything else in the library follows from it. A handler is a function, so middleware is a function that wraps a handler, and a stack of middleware is a composition of functions rather than a class hierarchy. Nothing in the model requires you to inherit from a base class or to implement an interface with a dozen methods.

The project states that this was inspired by Connect for NodeJS and Rack for Ruby, which tells you the lineage of the design and also its audience. Shelf is the Dart answer to a question those two libraries answered for their ecosystems, and the borrowing is deliberate rather than accidental.

The payload types are part of the simplicity. The model lets you return a simple string or a byte stream, with the same handler shape for both, so a middleware does not need to know in advance whether the thing below it produces text or binary data. In practice that is what allows a compression middleware, a static file handler and a JSON handler to be composed without special cases.

Synchronous and asynchronous handlers mix without ceremony

The feature list claims that Shelf can trivially mix and match synchronous and asynchronous processing. The claim sounds like a library detail and is actually an architectural one, because the alternative is a design where you pick sync or async once, at the framework level, and never mix them.

A pipeline where one layer reads a file asynchronously, the next one filters synchronously in memory, and the last one writes to the socket has to express all three without the whole pipeline taking a shape decided at the top. Shelf's single function signature is what allows that, and it is why a codebase that starts synchronous can grow an asynchronous data source later without rewriting the handlers around it.

The same reason explains the small set of simple types. In a library whose job is to be composed, every type you expose is a type somebody has to hold compatible with, and a large type system would push that cost onto the people writing the middleware. Shelf keeps the vocabulary narrow so the ecosystem, which is where most of the work happens, stays consistent.

The trade-off is that Shelf is deliberately not opinionated about the things a full framework usually decides for you, such as dependency injection, configuration, templating and routing. Routing exists, but in a separate package, and the repository's package table makes that split explicit.

A monorepo of nine packages, each with one job

The repository is not a single package. It is a monorepo with a pkgs/ directory, a mono_repo.yaml file, a shared analysis_options.yaml, a tool/ directory and a vendor/ directory, and the package table in the README lists what lives inside it.

The core package is shelf, described as a model for web server middleware that encourages composition and easy reuse. Around it sit seven more published packages. shelf_packages_handler serves a packages/ directory. shelf_proxy proxies HTTP requests to another server. shelf_router is a request router with support for URL parameters, nested routers and routers generated from source annotations. shelf_router_generator is the package:build compatible builder that generates those routers. shelf_static adds static file server support. shelf_test_handler makes it easy to test HTTP interactions. shelf_web_socket wires up a listener for every connection.

The ninth entry is the interesting one, because it is not published. _shelf_compliance holds HTTP/1.1 compliance and hardening tests, and its version column says not published.

The consequence for a user is that the pieces are versioned separately. The recent release list shows shelf_proxy at v1.0.5 and shelf_router_generator at v1.1.5, released on different dates, which is normal for independently published packages inside one repository and means your dependency list has more than one version to think about.

Routers can be written by hand or generated from annotations

The router story is the part of the ecosystem that has grown the most, and it has two levels.

At the first level, shelf_router is a router you write by hand, and the description calls it a convenient request router for the shelf web-framework. It supports URL parameters, and it supports nested routers, which means a group of routes can be mounted under a prefix and treated as a unit.

At the second level, routers can be generated from source annotations. The shelf_router_generator package is a package:build compatible builder that generates request routers based on those annotations. The two packages are deliberately separate, and the reason is visible in the descriptions: the router is a runtime thing you depend on, the generator is a build-time thing you only need in the project that owns the annotated source.

The split is what makes the generated approach usable in a real repository. The application depends on shelf_router at runtime and on the generator in its dev dependencies, and the generated code is an ordinary Dart file that can be read, reviewed and debugged like any other.

For a team choosing between the two, the annotation approach wins when routes number in the dozens and the URLs are part of an API contract, and the hand written router wins when there are a handful of routes and you would rather not add a build step to the loop.

Shelf is not a server, and the package table proves it

The clearest limitation is visible in the repository layout rather than hidden in the documentation. The package list contains middleware, routers, handlers, a proxy, a static file server, a test helper and a web socket handler. It contains no server.

Shelf is a model for web server middleware, and something has to accept TCP connections, speak HTTP/1.1 over them, and hand the result to your handler as a request object. That part is not in this repository, and a project using Shelf has to supply it.

This is not a criticism, it is a division of labour, and it is the same division the inspiration projects make. But it does change what you are adopting. If you evaluate Shelf as a web framework, you will find it missing the pieces a framework is expected to have. If you evaluate it as a middleware model, the small surface is exactly the argument.

The same applies to the other things a framework usually provides. There is no configuration system, no dependency injection container, no template engine and no session handling in the package list. Those are separate packages in the wider Dart ecosystem, and choosing Shelf means choosing them individually.

The compliance suite is where the sharp edges are documented

The unpublished package is the most telling one in the table.

_shelf_compliance is described as HTTP/1.1 compliance and hardening tests. It is not published, so it does not appear on pub.dev and it does not appear in a dependency list, but it lives in the same repository and it is maintained with the same care as the packages that are published.

What that arrangement buys is a definition of correct behaviour that the ecosystem agrees on. A handler that returns a response with an invalid status line, or that mishandles a chunked body, or that does something wrong when a client disconnects mid-response, has to pass these tests before the other packages can be relied on. A middleware ecosystem is only as good as its least careful contributor, and a shared compliance suite is the mechanism that keeps the floor from dropping.

The hardening part of the description matters as much as the compliance part. A middleware sits between the network and your code, so the failure modes it introduces are the ones an attacker reaches first, and having the edge cases encoded as a test suite rather than as folklore is worth more than it sounds.

For a team choosing a Shelf handler from the ecosystem, this is also the yardstick. Read the compliance tests before you read the README of any particular handler.

Releases, licensing and how Dart packages get published

The repository is not archived, and the last push was on 2026-10-16. The recent releases are shelf_router_generator v1.1.5 on 2026-06-25, shelf_proxy v1.0.5 on 2026-04-23 and shelf_router_generator v1.1.4 on 2026-02-26, which is a reminder that a single repository here publishes several independent version lines.

The licence is BSD-3-Clause, recorded in the LICENSE file at the top of the repository, and it applies across the monorepo.

The release process itself is not described in the README, and the project says so, pointing to the publishing automation and release process documentation in the Dart language ecosystem wiki. That is a deliberate pointer, because publishing to pub.dev has rules around versioning and dependency constraints that are enforced by the package site rather than by the repository, and the CI badge in the README is a Dart CI workflow rather than a bespoke build.

The repository also has a CONTRIBUTING file and a shared analysis_options.yaml, which is where a Dart project's lint configuration usually lives, and a.gitmodules entry, which is consistent with the vendor/ directory being used for something that is pulled in rather than written here.

The main package is published on pub.dev as shelf, and the README points to the package's own readme in pkgs/shelf/ for the detailed documentation rather than repeating it at the top level.

Editorial conclusion

Adopt Shelf when you are writing a Dart HTTP service and want a composable middleware model with routers, static files, proxies and web sockets that share one handler contract. Do not adopt it expecting a complete server or a full framework, because the repository contains no server package and the router and static file support live in separate published packages. Verify first which of the eight published packages you actually need and pin those versions, since they are released independently of each other, and read the compliance package's behaviour, because _shelf_compliance is what defines the HTTP/1.1 edge cases your handlers will meet in production.

Frequently asked questions

What is Shelf in Dart?

Shelf is a model for web server middleware. A handler is a function that takes a request and returns a response, and middleware composes by wrapping handlers. The project states it was inspired by Connect for NodeJS and Rack for Ruby.

Does Shelf include an HTTP server?

No. The repository's package list covers middleware, routing, static files, proxying, web sockets and test helpers, and contains no server package. A project using Shelf supplies the server that accepts connections and passes requests to its handlers.

Which packages are in the shelf repository?

shelf, shelf_packages_handler, shelf_proxy, shelf_router, shelf_router_generator, shelf_static, shelf_test_handler and shelf_web_socket are published, and _shelf_compliance, which holds HTTP/1.1 compliance and hardening tests, is not published.

How does shelf_router_generator work?

It is a package:build compatible builder that generates request routers for the shelf web-framework based on source annotations. The generated router is used at runtime through shelf_router, so the generator is a development dependency rather than a runtime one.

Can I mix synchronous and asynchronous handlers in Shelf?

Yes. The project lists trivially mixing and matching synchronous and asynchronous processing as a core property, which follows from the single function shape where the request is the argument and the response is the return value.

What licence is Shelf published under?

BSD-3-Clause, with the LICENSE file at the root of the dart-lang/shelf repository covering the monorepo and its packages.

Official sources

  1. dart-lang/shelf on GitHub
  2. License: BSD-3-Clause
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dart-lang-shelf.svg)](https://hysenlabs.com/projects/dart-lang-shelf)

Was this useful?