FastAPI plugin for automatic OpenAPI schema generation from msgspec structs. Enables Swagger UI documentation and TypeScript type generation.
- β¨ Automatic OpenAPI schema generation from msgspec structs
- π Swagger UI integration - See your msgspec types in
/docs - π· TypeScript type generation support via
openapi-typescript - π Zero runtime overhead - Schema generation happens once at startup
- π― Type-safe - Full type hints and mypy compatibility
- π§ Easy integration - Single line of code to enable
pip install fastapi-msgspec-openapifrom typing import Any
from fastapi import FastAPI
import msgspec
from fastapi_msgspec_openapi import MsgSpecPlugin
# Define your msgspec structs
class User(msgspec.Struct):
id: int
name: str
email: str
app = FastAPI()
# Inject the plugin
MsgSpecPlugin.inject(app)
@app.get("/user", response_model=Any)
async def get_user() -> User:
return msgspec.to_builtins(User(id=1, name="Alice", email="alice@example.com"))Now visit /docs - your msgspec structs will appear in the Swagger UI! π
msgspec is one of the fastest Python serialization libraries, but FastAPI doesn't natively generate OpenAPI schemas for msgspec structs. This plugin bridges that gap.
This plugin works great alongside fastapi-msgspec for complete msgspec integration:
from fastapi import FastAPI
from fastapi_msgspec.responses import MsgSpecJSONResponse # Fast serialization
from fastapi_msgspec_openapi import MsgSpecPlugin # OpenAPI docs
app = FastAPI(default_response_class=MsgSpecJSONResponse)
MsgSpecPlugin.inject(app)
# Now you have both:
# β
Fast msgspec serialization (runtime performance)
# β
Full OpenAPI documentation (developer experience)Performance: msgspec is significantly faster than Pydantic for serialization:
- 5-10x faster serialization
- Lower memory usage
- Native JSON encoding
When to use this plugin:
- β High-throughput APIs
- β Performance-critical services
- β You want msgspec speed + OpenAPI docs
- β TypeScript type generation needed
When to stick with Pydantic:
- β Complex validation logic (Pydantic has richer validation)
- β ORM integration (Pydantic works better with SQLAlchemy)
- β You don't need extreme performance
Generate TypeScript types from your OpenAPI schema:
# Generate types
npx openapi-typescript http://localhost:8000/openapi.json -o api-types.ts// Use in your frontend
import type { components } from "./api-types";
type User = components["schemas"]["User"];
const user: User = {
id: 1,
name: "Alice",
email: "alice@example.com",
};class Address(msgspec.Struct):
street: str
city: str
class User(msgspec.Struct):
id: int
name: str
address: Address # Nested struct
@app.get("/user", response_model=Any)
async def get_user() -> User:
return msgspec.to_builtins(
User(
id=1,
name="Alice",
address=Address(street="123 Main St", city="NYC")
)
)Both User and Address schemas will be generated automatically!
from typing import Optional
class Response(msgspec.Struct):
user: Optional[User] = None
users: list[User] = []
@app.get("/response", response_model=Any)
async def get_response() -> Response:
return msgspec.to_builtins(Response(users=[...]))The plugin handles Optional, list, and other generic types automatically.
- Route scanning - Detects msgspec structs in route type hints
- Schema generation - Uses
msgspec.json.schema_components()for native OpenAPI schemas - Response updates - Patches FastAPI's OpenAPI schema to reference your structs
- Caching - Schema generation happens once, then cached
FastAPI tries to validate/serialize the response using Pydantic when you specify a response_model. Since we're using msgspec structs (not Pydantic models), we use Any to bypass FastAPI's response handling:
@app.get("/user", response_model=Any) # Tells FastAPI: "I'll handle serialization"
async def get_user() -> User: # Plugin reads this for OpenAPI schema
return msgspec.to_builtins(...) # We handle serialization ourselvesThe plugin reads the return type hint (-> User) for schema generation, while response_model=Any prevents FastAPI from interfering.
You may see type checker warnings like:
return msgspec.to_builtins(user) # "Returning Any from function declared to return User"This is expected - msgspec.to_builtins() returns Any because it converts to dict/list at runtime. You can suppress this with:
return msgspec.to_builtins(user) # type: ignore[return-value]Or configure your type checker to ignore this pattern:
# pyproject.toml
[tool.mypy]
[[tool.mypy.overrides]]
module = "your_app.*"
disable_error_code = ["return-value"]Structs must be defined at module level for Python's get_type_hints() to resolve them properly:
# β
Good - Module level
class User(msgspec.Struct):
id: int
# β Bad - Inside function
def create_app():
class User(msgspec.Struct): # Won't be detected
id: intThis is a Python limitation, not specific to this plugin.
FastAPI's response_model expects Pydantic models. If we could use msgspec structs directly, we wouldn't need this plugin! The whole purpose is to:
- Use msgspec for performance (serialization)
- Generate OpenAPI schemas (documentation)
- Keep type hints accurate (developer experience)
This plugin bridges the gap by extracting schemas from type hints while letting you handle serialization with msgspec.
- Python 3.10+
- FastAPI 0.100.0+
- msgspec 0.18.0+
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License - see LICENSE file for details
Created by S3wnkin
Inspired by:
- msgspec by Jim Crist-Harif
- FastAPI by SebastiΓ‘n RamΓrez
- fastapi-msgspec by Iurii Skorniakov