make
Generate production-ready FastAPI modules and individual files. The primary FastKit CLI command — use make module for a full stack at once, or individual sub-commands to generate specific files.
make module
Generates a complete module with all six files in one command — the fastest path from idea to working endpoint.
Options
| Option | Short | Default | Description |
|---|---|---|---|
--dir | -d | modules | Root directory where the module folder is created |
--async | -a | False | Generate async repository, service, and router |
--signals | -s | False | Also generate signals.py and listeners.py |
--force | -f | False | Overwrite existing files |
Examples
# Basic sync module
fastkit make module Invoice
# Async module
fastkit make module Invoice --async
# Module with signals and listeners
fastkit make module Invoice --signals
# Async module with signals
fastkit make module Invoice --async --signals
# Custom output directory
fastkit make module Invoice --dir src/modules
# Compound name (PascalCase or snake_case — both work)
fastkit make module InvoiceItem
fastkit make module invoice_item # same result
# Overwrite existing files
fastkit make module Invoice --force
Generated files
BaseWithTimestamps and IntIdMixinCreate, Update, and Response schemasRepository (or AsyncRepository with --async)BaseCrudService (or AsyncBaseCrudService with --async)--signals--signalsmake module also automatically registers the new model in alembic/env.py so it's picked up by the next fastkit migrate make.
make model
Generates only the SQLAlchemy model file. Useful when you have an existing module and need to add a model separately.
| Option | Short | Default | Description |
|---|---|---|---|
--path | -p | . | Target directory for the generated file |
--force | -f | False | Overwrite existing file |
fastkit make model Invoice
fastkit make model Invoice --path modules/invoices
Generated models.py
from fastkit_core.database import BaseWithTimestamps, IntIdMixin
# from fastkit_core.database import UUIDMixin, SoftDeleteMixin, SlugMixin
class Invoice(BaseWithTimestamps, IntIdMixin):
__tablename__ = "invoices"
# Define your fields here
make schema
Generates only the Pydantic schemas file with Create, Update, and Response classes.
| Option | Short | Default | Description |
|---|---|---|---|
--path | -p | . | Target directory for the generated file |
--force | -f | False | Overwrite existing file |
fastkit make schema Invoice
fastkit make schema Invoice --path modules/invoices
Generated schemas.py
from fastkit_core.validation import BaseSchema
class InvoiceCreate(BaseSchema):
pass # Define your fields here
class InvoiceUpdate(BaseSchema):
pass # All fields optional for partial updates
class InvoiceResponse(BaseSchema):
id: int
model_config = {"from_attributes": True}
make repository
Generates only the repository file. Supports both sync and async variants.
| Option | Short | Default | Description |
|---|---|---|---|
--path | -p | . | Target directory for the generated file |
--async | -a | False | Generate async repository using AsyncRepository |
--force | -f | False | Overwrite existing file |
fastkit make repository Invoice
fastkit make repository Invoice --async
fastkit make repository Invoice --path modules/invoices
from fastkit_core.database import Repository
from sqlalchemy.orm import Session
from .models import Invoice
class InvoiceRepository(Repository[Invoice]):
def __init__(self, session: Session):
super().__init__(Invoice, session)
from fastkit_core.database import AsyncRepository
from sqlalchemy.ext.asyncio import AsyncSession
from .models import Invoice
class InvoiceRepository(AsyncRepository[Invoice]):
def __init__(self, session: AsyncSession):
super().__init__(Invoice, session)
make service
Generates only the service file. The place for your business logic — add lifecycle hooks and custom methods after generation.
| Option | Short | Default | Description |
|---|---|---|---|
--path | -p | . | Target directory for the generated file |
--async | -a | False | Generate async service using AsyncBaseCrudService |
--force | -f | False | Overwrite existing file |
fastkit make service Invoice
fastkit make service Invoice --async
from fastkit_core.services import BaseCrudService
from sqlalchemy.orm import Session
from .models import Invoice
from .schemas import InvoiceCreate, InvoiceUpdate, InvoiceResponse
from .repository import InvoiceRepository
class InvoiceService(BaseCrudService[Invoice, InvoiceCreate, InvoiceUpdate, InvoiceResponse]):
def __init__(self, session: Session):
super().__init__(
InvoiceRepository(session),
response_schema=InvoiceResponse
)
# Add your business logic, lifecycle hooks, and custom methods here
from fastkit_core.services import AsyncBaseCrudService
from sqlalchemy.ext.asyncio import AsyncSession
from .models import Invoice
from .schemas import InvoiceCreate, InvoiceUpdate, InvoiceResponse
from .repository import InvoiceRepository
class InvoiceService(AsyncBaseCrudService[Invoice, InvoiceCreate, InvoiceUpdate, InvoiceResponse]):
def __init__(self, session: AsyncSession):
super().__init__(
InvoiceRepository(session),
response_schema=InvoiceResponse
)
# Add your async business logic, lifecycle hooks, and custom methods here
make signals
Generates signals.py and listeners.py for an existing module. Use this when you want to add the signal system to a module that was created without the --signals flag.
| Option | Short | Default | Description |
|---|---|---|---|
--path | -p | . | Target directory for the generated files |
--force | -f | False | Overwrite existing files |
# Add signals to an existing module
fastkit make signals Invoice
# Target a specific path
fastkit make signals Invoice --path modules/invoices
# Overwrite existing signals.py and listeners.py
fastkit make signals Invoice --force
Generated signals.py
from fastkit_core.events import Signal
__all__ = ['invoice_created', 'invoice_updated', 'invoice_deleted']
invoice_created = Signal('invoice.created')
invoice_updated = Signal('invoice.updated')
invoice_deleted = Signal('invoice.deleted')
Generated listeners.py
from .signals import invoice_created, invoice_updated, invoice_deleted
# @invoice_created.connect
# async def on_invoice_created(payload: dict) -> None:
# """Called after an Invoice is successfully created."""
# pass
# @invoice_updated.connect
# async def on_invoice_updated(payload: dict) -> None:
# """Called after an Invoice is successfully updated."""
# pass
# @invoice_deleted.connect
# async def on_invoice_deleted(payload: dict) -> None:
# """Called after an Invoice is successfully deleted."""
# pass
Important: listeners.py must be imported at application startup or receivers will never register. Add import modules.invoices.listeners to your main.py lifespan. Without this import, signals fire silently with no effect — no error, just silence.
Using signals in the service
After generating the files, fire signals from your service lifecycle hooks. The service only calls send() — it has no knowledge of who is listening:
from .signals import invoice_created, invoice_deleted
class InvoiceService(AsyncBaseCrudService[...]):
async def after_create(self, instance: Invoice) -> None:
await invoice_created.send({
'id': instance.id,
'customer_id': instance.customer_id,
'total': instance.total,
})
async def after_delete(self, id: int) -> None:
await invoice_deleted.send({'id': id})
For full documentation on the Signal system — error isolation, payload conventions, testing, and custom backends — see fastkit-core → Events.
make router
Generates only the router file with all five CRUD endpoints pre-wired to the service.
| Option | Short | Default | Description |
|---|---|---|---|
--path | -p | . | Target directory for the generated file |
--async | -a | False | Generate async router using get_async_db |
--force | -f | False | Overwrite existing file |
fastkit make router Invoice
fastkit make router Invoice --async
Generated endpoints
| Handler | Method | Path | Description |
|---|---|---|---|
index | GET | /invoices | Paginated list with page and per_page params |
show | GET | /invoices/{id} | Single record — 404 if not found |
store | POST | /invoices | Create — returns 201 |
update | PUT | /invoices/{id} | Update — partial update supported |
destroy | DELETE | /invoices/{id} | Delete — returns 204 |
from fastapi import APIRouter, Depends
from fastkit_core.database import get_db
from fastkit_core.http import success_response, paginated_response
from sqlalchemy.orm import Session
from .schemas import InvoiceCreate, InvoiceUpdate
from .service import InvoiceService
router = APIRouter(prefix="/invoices", tags=["invoices"])
def get_service(session: Session = Depends(get_db)) -> InvoiceService:
return InvoiceService(session)
@router.get("/")
def index(page: int = 1, per_page: int = 20, svc: InvoiceService = Depends(get_service)):
items, meta = svc.paginate(page=page, per_page=per_page)
return paginated_response(items=items, pagination=meta)
@router.get("/{id}")
def show(id: int, svc: InvoiceService = Depends(get_service)):
return success_response(data=svc.find_or_fail(id).model_dump())
@router.post("/", status_code=201)
def store(data: InvoiceCreate, svc: InvoiceService = Depends(get_service)):
return success_response(data=svc.create(data).model_dump())
@router.put("/{id}")
def update(id: int, data: InvoiceUpdate, svc: InvoiceService = Depends(get_service)):
return success_response(data=svc.update(id, data).model_dump())
@router.delete("/{id}", status_code=204)
def destroy(id: int, svc: InvoiceService = Depends(get_service)):
svc.delete(id)
from fastapi import APIRouter, Depends
from fastkit_core.database import get_async_db
from fastkit_core.http import success_response, paginated_response
from sqlalchemy.ext.asyncio import AsyncSession
from .schemas import InvoiceCreate, InvoiceUpdate
from .service import InvoiceService
router = APIRouter(prefix="/invoices", tags=["invoices"])
def get_service(session: AsyncSession = Depends(get_async_db)) -> InvoiceService:
return InvoiceService(session)
@router.get("/")
async def index(page: int = 1, per_page: int = 20, svc: InvoiceService = Depends(get_service)):
items, meta = await svc.paginate(page=page, per_page=per_page)
return paginated_response(items=items, pagination=meta)
@router.get("/{id}")
async def show(id: int, svc: InvoiceService = Depends(get_service)):
return success_response(data=(await svc.find_or_fail(id)).model_dump())
@router.post("/", status_code=201)
async def store(data: InvoiceCreate, svc: InvoiceService = Depends(get_service)):
return success_response(data=(await svc.create(data)).model_dump())
@router.put("/{id}")
async def update(id: int, data: InvoiceUpdate, svc: InvoiceService = Depends(get_service)):
return success_response(data=(await svc.update(id, data)).model_dump())
@router.delete("/{id}", status_code=204)
async def destroy(id: int, svc: InvoiceService = Depends(get_service)):
await svc.delete(id)
Common Options
All make sub-commands share these patterns:
--force / overwriting files
By default FastKit CLI refuses to overwrite existing files to prevent accidental data loss. Use --force to override:
# Will fail if modules/invoices/ already exists
fastkit make module Invoice
# Overwrites all files in modules/invoices/
fastkit make module Invoice --force
--force overwrites files without confirmation. Commit your changes before using it.
--async vs sync
Choose sync or async based on your project's database setup:
- Uses
Sessionfromget_db - Uses
Repository - Uses
BaseCrudService - Standard
defhandlers - Simpler — good for most projects
- Uses
AsyncSessionfromget_async_db - Uses
AsyncRepository - Uses
AsyncBaseCrudService async defhandlers withawait- Better for high-concurrency I/O workloads
Sub-command summary
| Sub-command | Generates | Supports --async |
|---|---|---|
make module | All 6 files (+ signals with --signals) | ✓ |
make model | models.py | — |
make schema | schemas.py | — |
make repository | repository.py | ✓ |
make service | service.py | ✓ |
make router | router.py | ✓ |
make signals | signals.py + listeners.py | — |