Building Your First API¶
Warning
The current page still doesn't have a translation for this language.
But you can help translating it: Contributing.
In this guide, we’ll build a small API from scratch using Ravyn. You’ll define routes, accept path and query parameters, validate request bodies, and return structured responses.
What you'll build¶
A tiny API with:
GET /to confirm the app is running.GET /users/{user_id}using a path parameter.GET /searchusing query parameters.POST /userswith request validation.
Mental model first¶
A Ravyn request usually follows this flow:
HTTP Request
-> route match (@get/@post/...)
-> parameter parsing (path/query/body)
-> validation (type hints + Pydantic)
-> handler execution
-> response serialization
HTTP Response
If you keep this flow in mind, each feature in the next chapters will feel predictable.
Step 1: Create and run a minimal app¶
from ravyn import Ravyn, get
@get("/")
def home() -> dict:
return {"message": "Welcome to your API!"}
app = Ravyn(routes=[home])
Run it:
palfrey main:app --reload
Open http://127.0.0.1:8000/.
Step 2: Add path and query parameters¶
Path parameter¶
from ravyn import get
@get("/users/{user_id}")
def get_user(user_id: int) -> dict:
return {"user_id": user_id, "name": f"User {user_id}"}
GET /users/42returns{"user_id": 42, "name": "User 42"}.user_idis converted tointautomatically.
Query parameters¶
from ravyn import Query, get
@get("/search")
def search(term: str = Query(...), limit: int = Query(10)) -> dict:
return {"term": term, "limit": limit}
Try:
GET /search?term=ravynGET /search?term=ravyn&limit=5
Step 3: Accept and validate JSON body data¶
from pydantic import BaseModel
from ravyn import post
class CreateUser(BaseModel):
name: str
age: int
@post("/users")
def create_user(user: CreateUser) -> dict:
return {"created": True, "user": user.model_dump()}
Test with curl:
curl -X POST http://127.0.0.1:8000/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "age": 30}'
Invalid data is rejected automatically with a structured error response.
Step 4: Put everything together¶
from pydantic import BaseModel
from ravyn import Query, Ravyn, get, post
class CreateUser(BaseModel):
name: str
age: int
@get("/")
def home() -> dict:
return {"message": "Welcome to your API!"}
@get("/users/{user_id}")
def get_user(user_id: int) -> dict:
return {"user_id": user_id, "name": f"User {user_id}"}
@get("/search")
def search(term: str = Query(...), limit: int = Query(10)) -> dict:
return {"term": term, "limit": limit}
@post("/users")
def create_user(user: CreateUser) -> dict:
return {"created": True, "user": user.model_dump()}
app = Ravyn(routes=[home, get_user, search, create_user])
Common mistakes at this stage¶
1. Missing return types¶
Always annotate handler return types (-> dict, -> Model, etc.).
2. Treating query/path values as untyped strings¶
Use type annotations (int, bool, UUID) so Ravyn validates and converts for you.
3. Mixing too many concepts at once¶
Keep each route focused while learning: one feature per endpoint.
Related pages¶
What's Next?¶
Continue to Request and Response Models to go deeper into schema design and validation behavior.