MicroAPI provides multiple ways to run code during application startup and shutdown: individual hooks, and lifespan context managers.
Register functions that run when the server starts or stops:
from microapi import MicroAPI
app = MicroAPI()
@app.on_startup
async def startup():
print("Server starting...")
await initialize_database()
await warm_up_cache()
@app.on_shutdown
async def shutdown():
print("Server stopping...")
await close_database()
await flush_metrics()You can register multiple hooks — they execute in registration order:
@app.on_startup
async def init_db():
app.db = await create_pool()
@app.on_startup
async def init_cache():
app.cache = await connect_redis()
@app.on_shutdown
async def cleanup_db():
await app.db.close()
@app.on_shutdown
async def cleanup_cache():
await app.cache.close()Both sync and async functions are supported:
@app.on_startup
def setup_logging():
logging.basicConfig(level=logging.INFO)app = MicroAPI(
on_startup=[init_db, init_cache],
on_shutdown=[cleanup_db, cleanup_cache],
)For more control, use a lifespan context manager. This is the recommended approach for managing resources that need both setup and teardown:
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app):
# Startup
db = await create_database_pool()
cache = await connect_redis()
print("Resources initialized")
yield # Server runs here
# Shutdown
await cache.close()
await db.close()
print("Resources cleaned up")
app = MicroAPI(lifespan=lifespan)You can also pass a raw async generator function (without @asynccontextmanager):
async def lifespan(app):
# Startup
db = await create_pool()
yield
# Shutdown
await db.close()
app = MicroAPI(lifespan=lifespan)MicroAPI automatically detects whether you've passed a raw async generator or a decorated context manager and handles both correctly.
You can use both hooks and lifespan together. The execution order is:
on_startuphooks run first- Lifespan startup (before
yield) - Server runs
- Lifespan shutdown (after
yield) on_shutdownhooks run last
@asynccontextmanager
async def lifespan(app):
print("2. Lifespan startup")
yield
print("4. Lifespan shutdown")
app = MicroAPI(lifespan=lifespan)
@app.on_startup
async def hook_start():
print("1. on_startup hook")
@app.on_shutdown
async def hook_stop():
print("5. on_shutdown hook")Enable hot reload to automatically restart the server when source files change:
app.run(
transport=HTTPTransport(port=8080),
reload=True,
)Or via CLI:
microapi run server.main:app --reloadHot reload uses watchfiles to monitor the current directory for .py file changes. When a change is detected, the server process is restarted.
- MicroAPI spawns the server in a subprocess
watchfilesmonitors the working directory for.pychanges- On change, the subprocess is terminated and restarted
- Signal handlers are properly cleaned up between restarts
- Only
.pyfiles in the current directory tree are watched - Changes to dependencies outside the project won't trigger reload
- Not recommended for production — use a process manager instead
MicroAPI installs signal handlers for graceful shutdown:
- SIGINT (Ctrl+C) — triggers shutdown
- SIGTERM — triggers shutdown (used by Docker, systemd, etc.)
When a signal is received:
- The shutdown event is set
- The transport server is stopped gracefully
- Lifespan shutdown runs
on_shutdownhooks execute- Signal handlers are removed (clean for hot reload)
For production, use a process manager instead of hot reload:
# /etc/systemd/system/my-service.service
[Unit]
Description=My MicroAPI Service
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/my-service
ExecStart=/opt/my-service/.venv/bin/python -m server.main
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetFROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install microapi[http]
CMD ["python", "server/main.py"][program:my-service]
command=/opt/my-service/.venv/bin/python -m server.main
directory=/opt/my-service
autostart=true
autorestart=true
stderr_logfile=/var/log/my-service.err.log
stdout_logfile=/var/log/my-service.out.log