DEV Community

Cover image for A New Life for aiohttp?
Daniil Grois
Daniil Grois

Posted on

A New Life for aiohttp?

Introduction

Once upon a time, aiohttp was a revolution in the Python world: the first asynchronous web framework that allowed developers to build fast, efficient, and truly ergonomic web applications using asyncio.

Years have passed, and the world has changed. We’ve seen the rise of FastAPI, Pydantic, Litestar, Starlette — all striving to make async development simpler, safer, and more powerful. But one thing hasn’t changed: aiohttp is still the foundation of the Python async ecosystem.

And today we’re introducing Rapidy — a new framework inspired by the best ideas of aiohttp, reimagined for 2025.


TL;DR

Rapidy is a modern asynchronous Python framework that rethinks the ideas of aiohttp.

It combines strict typing, support for both Pydantic v1/v2, DI through Dishka, mypy compatibility, and remains lightweight, minimalist, and backward-compatible with aiohttp.

  • 🔄 aiohttp compatible

  • ✅ Full mypy, Pydantic, and DI support

  • 🧩 Simplified routing, controllers, middleware

  • 🧰 Lifecycle hooks, content-type filtering

  • 🔧 Production-ready and open to contributors

Docs and code:

📚 Docs: https://rapidy.dev

⭐ GitHub: https://github.com/rAPIdy-org/rAPIdy


What can Rapidy do?

Native support for Pydantic v1 and v2 — validation and serialization out of the box.


Validate and serialize directly in handlers, and yes — you can use middleware for that too:

from pydantic import BaseModel from rapidy import Rapidy from rapidy.http import Body, Request, Header, StreamResponse, middleware, post from rapidy.typedefs import CallNext TOKEN_REGEXP = '^[Bb]earer (?P<token>[A-Za-z0-9-_=.]*)' class RequestBody(BaseModel): username: str password: str class ResponseBody(BaseModel): hello: str = 'rapidy' @middleware async def get_bearer_middleware( request: Request, call_next: CallNext, bearer_token: str = Header(alias='Authorization', pattern=TOKEN_REGEXP), ) -> StreamResponse: # process token here ...  return await call_next(request) @post('/') async def handler( body: RequestBody = Body(), ) -> ResponseBody: return ResponseBody() app = Rapidy( middlewares=[get_bearer_middleware], http_route_handlers=[handler], ) 
Enter fullscreen mode Exit fullscreen mode

Full mypy and strict typing support — Rapidy comes with its own mypy plugin:

# pyproject.toml [tool.mypy] plugins = [ "pydantic.mypy", "rapidy.mypy" # <-- enable rapidy plugin ] 
Enter fullscreen mode Exit fullscreen mode

Native integration with Dishka, one of the most elegant DI frameworks in the Python world.

https://github.com/reagento/dishka

Rapidy provides a Dependency Container with clear and predictable behavior:

from rapidy import Rapidy from rapidy.http import get from rapidy.depends import FromDI, provide, Provider, Scope class HelloProvider(Provider): @provide(scope=Scope.REQUEST) async def hello(self) -> str: return 'hello, Rapidy!' @get('/') async def handler(hello: FromDI[str]) -> dict: return {"message": hello} app = Rapidy( http_route_handlers=[handler], di_providers=[HelloProvider()], ) 
Enter fullscreen mode Exit fullscreen mode

Full backward compatibility with existing aiohttp code:

from rapidy import web from rapidy.http import HTTPRouter, get from pydantic import BaseModel # --- old functionality routes = web.RouteTableDef() @routes.get('/user') async def get_user_aiohttp(request: web.Request) -> web.Response: return web.Response(text='User aiohttp') v1_app = web.Application() v1_app.add_routes(routes) # --- new functionality @get('/user') async def get_user_rapidy() -> str: return 'User rapidy' v2_router = HTTPRouter('/v2', route_handlers=[get_user_rapidy]) # app app = web.Application( http_route_handlers=[v2_router], ) app.add_subapp('/v1', v1_app) 
Enter fullscreen mode Exit fullscreen mode

You can even use Rapidy's functionality inside your old code — pure magic.

Migration guide: https://rapidy.dev/latest/aiohttp_migration/


Controllers — enhanced class-based views:

from rapidy import Rapidy from rapidy.http import PathParam, controller, get, post, put, patch, delete @controller('/') class UserController: @get('/{user_id}') async def get_by_id(self, user_id: str = PathParam()) -> dict[str, str]: return {'user_id': user_id} @get() async def get_all_users(self) -> list[dict[str, str]]: return [{'name': 'John'}, {'name': 'Felix'}] @post() async def create_user(self) -> str: return 'ok' rapidy = Rapidy(http_route_handlers=[UserController]) 
Enter fullscreen mode Exit fullscreen mode

Better and simpler routing — say goodbye to awkward Application nesting:

from rapidy import Rapidy from rapidy.http import HTTPRouter, controller, get @get('/hello') async def hello_handler() -> dict[str, str]: return {'hello': 'rapidy'} @controller('/hello_controller') class HelloController: @get() async def get_hello(self) -> dict[str, str]: return {'hello': 'rapidy'} api_router = HTTPRouter('/api', [hello_handler, HelloController]) rapidy = Rapidy(http_route_handlers=[api_router]) 
Enter fullscreen mode Exit fullscreen mode

Convenient lifespan management — control the app lifecycle your way:

from contextlib import asynccontextmanager from typing import AsyncGenerator from rapidy import Rapidy def hello() -> None: print('hello') @asynccontextmanager async def bg_task() -> AsyncGenerator[None, None]: try: print('starting background task') yield finally: print('finishing background task') rapidy = Rapidy( lifespan=[bg_task()], on_startup=[hello], on_shutdown=[hello], on_cleanup=[hello], ) 
Enter fullscreen mode Exit fullscreen mode

Unified Body extraction and full control over accepted content types:

from pydantic import BaseModel from rapidy.http import post, Body, ContentType class UserData(BaseModel): username: str password: str @post('/') async def handler_json( user_data: UserData = Body(), # or  user_data: UserData = Body(content_type=ContentType.json), # or  user_data: UserData = Body(content_type='application/json'), ) -> ...: @post('/') async def handler_x_www( user_data: UserData = Body(content_type=ContentType.x_www_form), # or  user_data: UserData = Body(content_type='application/x-www-form-urlencoded'), ) -> ...: @post('/') async def handler( user_data: UserData = Body(content_type=ContentType.m_part_form_data), # or  user_data: UserData = Body(content_type='multipart/form-data'), ) -> ...: # ... and others ... 
Enter fullscreen mode Exit fullscreen mode

Full and readable documentation is available at:

https://rapidy.dev

Rapidy includes much more than shown here — explore more features in the docs or future posts.


⁉️ Why Another Framework?

Because Python evolves.

  • In 2016, aiohttp was a breakthrough.

  • In 2020, FastAPI showed how expressive and declarative an API could be.

  • In 2025, Rapidy proves you can have it all — without compromise.


💡 Why Contribute to Rapidy?

Rapidy was built with care and attention to detail. Its modular architecture, clean structure, and comprehensive internal documentation are designed to make contributing as easy and pleasant as possible.

I’ve worked hard to make the codebase friendly and accessible: clearly separated layers, zero magic, and transparent component mechanics.

If you’ve ever wanted to contribute to a framework but felt intimidated — Rapidy is the project where you’ll feel confident from the very beginning.

We also welcome discussions, ideas, and feedback. This is not just a repo — it’s a community where anyone can shape the future of modern Python development.


🚀 What’s Next?

  • OpenAPI is almost done and will be released very soon.

  • Boilerplates for fast microservice scaffolding are on the way.

  • Drafts for WebSocket and gRPC support are in progress.


📌 In Summary

This project is not an attempt to replace aiohttp. It's an attempt to give it a second life — with the same spirit of simplicity, the same respect for clarity and control, but with the features modern development demands.

And we would be truly honored if Andrey Svetlov — the person who started it all — joined us on this journey.


Try It Out

The project is open and already in active use:

👉 https://github.com/rAPIdy-org/rAPIdy

Docs, examples, discussions — all open.

If you once loved aiohttp but have been waiting for something fresh — Rapidy is here.

Top comments (0)