Skip to content

Quick Start

Build your first API resource in under 5 minutes.

Prerequisites

Make sure you've completed the Installation steps before continuing.

Step 1: Start the Server

bash
# Activate virtual environment
source venv/bin/activate

# Start development server
fastpy serve

Server Running

Your API is now running at http://localhost:8000

Step 2: Explore the API

Visit the interactive documentation:

Step 3: Create Your First Resource

Let's create a complete blog post resource:

bash
fastpy make:resource Post \
  -f title:string:required,max:200 \
  -f slug:slug:unique \
  -f body:text:required \
  -f published:boolean:default:false \
  -f published_at:datetime:nullable \
  -m -p

This generates:

FileDescription
app/models/post.pySQLModel model with Model Concerns
app/controllers/post_controller.pyActive Record-based CRUD logic
app/routes/post_routes.pyRoutes with Route Model Binding
alembic/versions/xxx_create_posts.pyDatabase migration

Included by Default

  • Active Record - Post.create(), post.update(), post.delete()
  • Route Model Binding - Auto-resolve {id} params to model instances
  • Model Concerns - HasScopes, GuardsAttributes for clean queries

Step 4: Apply the Migration

bash
fastpy db:migrate -m "Create posts table"

Step 5: Register the Routes

Add your new routes to app/routes/__init__.py:

python
from app.routes.post_routes import router as post_router

# In the register_routes function
app.include_router(post_router, prefix="/api/posts", tags=["Posts"])

Step 6: Test Your Endpoints

bash
curl -X POST http://localhost:8000/api/posts \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "title": "My First Post",
    "body": "Hello, world!",
    "published": true
  }'
bash
curl http://localhost:8000/api/posts \
  -H "Authorization: Bearer YOUR_TOKEN"
bash
curl http://localhost:8000/api/posts/1 \
  -H "Authorization: Bearer YOUR_TOKEN"

Understanding the Generated Code

Model (app/models/post.py)
python
from app.models.base import BaseModel
from app.models.concerns import HasScopes, GuardsAttributes

class Post(BaseModel, HasScopes, GuardsAttributes, table=True):
    __tablename__ = "posts"

    title: str = Field(max_length=200)
    slug: str = Field(unique=True, index=True)
    body: str = Field(sa_column=Column(Text))
    published: bool = Field(default=False)
    published_at: Optional[datetime] = None

    # Mass assignment protection
    _fillable = ['title', 'slug', 'body', 'published', 'published_at']
    _guarded = ['id', 'created_at', 'updated_at', 'deleted_at']
Controller (app/controllers/post_controller.py) - Active Record
python
from app.models.post import Post

class PostController:
    @staticmethod
    async def get_all(skip: int = 0, limit: int = 100):
        """Uses Active Record pattern - no session needed"""
        return await Post.query().limit(limit).offset(skip).get()

    @staticmethod
    async def create(data: PostCreate):
        return await Post.create(**data.model_dump())

    @staticmethod
    async def update(id: int, data: PostUpdate):
        post = await Post.find_or_fail(id)
        await post.update(**data.model_dump(exclude_unset=True))
        return post

    @staticmethod
    async def delete(id: int):
        post = await Post.find_or_fail(id)
        await post.delete()  # Soft delete
Routes (app/routes/post_routes.py) - Route Model Binding
python
from app.utils.binding import bind_or_fail

@router.get("/")
async def list_posts(current_user: User = Depends(get_current_active_user)):
    """No session needed - Active Record handles it"""
    return await PostController.get_all()

@router.get("/{id}")
async def get_one(post: Post = bind_or_fail(Post)):
    """Route model binding - auto-resolved from {id}"""
    return post

@router.put("/{id}")
async def update(data: PostUpdate, post: Post = bind_or_fail(Post)):
    await post.update(**data.model_dump(exclude_unset=True))
    return post

What's Next?

Released under the MIT License.