How to Add Custom API Endpoints to the FastAPI Router in Open Notebook
To add custom API endpoints to the FastAPI router in Open Notebook, create a new router module in api/routers/, define routes using APIRouter, and register it in api/main.py using app.include_router().
Open Notebook is an open-source knowledge management system built on FastAPI that organizes HTTP routes into modular router components under api/routers/. Extending the application's REST API requires following the established pattern where independent router modules are imported and mounted in api/main.py (lines 38-62). This guide demonstrates the exact implementation steps used in the lfnovo/open-notebook repository to ensure consistency with existing endpoints.
Understanding the Router Architecture
Open Notebook follows a modular FastAPI architecture that separates routing concerns into focused, reusable components.
Modular Router Organization
The codebase organizes API endpoints by domain in separate files within api/routers/. Existing implementations like sources.py and notebooks.py demonstrate the standard pattern: each module declares a module-level router = APIRouter() instance and defines async handler functions decorated with @router.get(), @router.post(), or other HTTP verbs. This separation keeps the main application file clean while allowing domain-specific logic to remain encapsulated.
Main Application Integration
The central FastAPI application instance lives in api/main.py, where all routers are imported and mounted under the /api prefix. According to the source code in lines 38-62, the application uses app.include_router() to register each router module, ensuring consistent URL prefixes and tag metadata for OpenAPI documentation. Global exception handlers defined in api/main.py (such as custom_http_exception_handler at lines 49-64) automatically wrap router-level HTTPException instances with proper CORS headers.
Creating a Custom Router Module
To add new functionality, you must create a router file that follows the established architectural patterns.
Step 1: Initialize the Router File
Create a new Python file in api/routers/ (for example, hello.py). Begin by importing APIRouter from FastAPI and instantiating a module-level router object:
# api/routers/hello.py
from fastapi import APIRouter, HTTPException, Query
from loguru import logger
from api.models import HelloResponse
router = APIRouter()
This router object serves as the decorator factory for your endpoint functions, exactly as implemented in existing modules like api/routers/sources.py.
Step 2: Define Pydantic Schemas
All request and response validation in Open Notebook uses Pydantic models centralized in api/models.py. For your custom endpoint, add the appropriate schema class to ensure consistent validation:
# api/models.py
from pydantic import BaseModel
class HelloResponse(BaseModel):
message: str
Reusing existing models from api/models.py is recommended when possible to maintain consistency across the API surface.
Step 3: Implement Endpoint Handlers
Define async functions decorated with the appropriate HTTP method and route path. The implementation should follow the error-handling patterns used throughout the repository, utilizing HTTPException for client errors and loguru for logging:
# api/routers/hello.py
@router.get("/hello", response_model=HelloResponse)
async def hello(name: str = Query("World", description="Name to greet")):
"""
Simple example endpoint that returns a greeting.
"""
logger.info(f"Received greeting request for name={name}")
if not name.isidentifier():
raise HTTPException(status_code=400, detail="Invalid name")
return HelloResponse(message=f"Hello, {name}!")
The router handles path operation definitions while the global exception handlers in api/main.py manage cross-cutting concerns like CORS headers.
Registering the Router in the Application
After creating the router module, you must explicitly register it with the FastAPI application instance.
Include Router Configuration
Open api/main.py and import your new router module alongside existing imports. Add the router to the application using include_router():
# api/main.py
from api.routers import hello # Import the new router module
# ... existing router imports ...
app.include_router(hello.router, prefix="/api", tags=["hello"])
This registration pattern (observed in lines 38-62 of api/main.py) mounts your endpoints at /api/hello and includes them in the auto-generated OpenAPI documentation under the specified tag.
Prefix and Tags Configuration
The prefix="/api" parameter ensures consistency with the existing API structure, while tags=["hello"] organizes endpoints in the interactive documentation. You can inspect the registration of built-in routers like sources and notebooks in api/main.py to verify the exact syntax used in the repository.
Error Handling and Data Persistence
Custom endpoints must integrate with Open Notebook's data access patterns and error handling infrastructure.
Global Exception Handling
The api/main.py file defines global exception handlers that automatically process HTTPException instances raised by your router. When you raise HTTPException(status_code=400, detail="Invalid name") in your endpoint, the custom_http_exception_handler (lines 49-64) wraps the response with appropriate CORS headers, ensuring frontend compatibility without requiring manual header management in each router.
Domain Layer Integration
For endpoints requiring database interaction, the repository provides two primary patterns:
- Direct Repository Access: Use
open_notebook/database/repository.pyfor generic CRUD operations - Service Layer Pattern: Follow the example in
api/sources_service.pywhere business logic is abstracted from the router
For simple operations, you may interact directly with domain objects in open_notebook/domain/, while complex workflows should implement a service layer to maintain separation of concerns.
Testing Your Custom Endpoint
The repository includes a test suite using FastAPI's TestClient. Create test files in the tests/ directory following this pattern:
# tests/test_hello.py
from fastapi.testclient import TestClient
from api.main import app
client = TestClient(app)
def test_hello_endpoint():
resp = client.get("/api/hello?name=Bob")
assert resp.status_code == 200
assert resp.json() == {"message": "Hello, Bob"}
Execute tests using uv run pytest tests/test_hello.py to verify your endpoint responds correctly and integrates properly with the FastAPI application lifecycle.
Summary
- Create router modules in
api/routers/usingAPIRouter()and standard FastAPI decorator patterns - Centralize schemas in
api/models.pyusing Pydantic for request/response validation - Register routers in
api/main.pyviaapp.include_router()with the/apiprefix (lines 38-62) - Leverage global exception handlers in
api/main.py(lines 49-64) for automatic CORS header management - Persist data through
open_notebook/database/repository.pyor domain objects inopen_notebook/domain/ - Test endpoints using FastAPI's
TestClientin thetests/directory
Frequently Asked Questions
How do I add authentication to my custom FastAPI endpoint in Open Notebook?
The repository handles authentication at the application level in api/main.py. When you register your router using app.include_router(), it inherits the global authentication middleware and dependency injection patterns configured in the main application. Check existing routers like api/routers/sources.py for examples of how to use FastAPI's Depends with authentication schemes already implemented in the codebase.
Can I use synchronous functions instead of async in my custom router?
Yes, FastAPI supports both async and sync endpoints. However, for consistency with the existing Open Notebook codebase—which uses async patterns for database operations and external service calls—implementing async def endpoints is recommended. If you must use synchronous code that blocks the event loop, run it in a thread pool or follow the pattern used in api/sources_service.py for background processing.
Where should I store business logic for my custom API endpoints?
Business logic should reside in the service layer (similar to api/sources_service.py) or directly in the domain layer (open_notebook/domain/), not in the router module itself. The router (api/routers/*.py) should handle HTTP concerns like request validation, response serialization, and error translation, while delegating business operations to domain objects or repository methods. This maintains the separation of concerns visible in the existing codebase.
How do I handle file uploads in my custom FastAPI router?
Follow the implementation pattern in api/routers/sources.py, which demonstrates handling file uploads with FastAPI's UploadFile and File dependencies. The router should accept the file parameter, potentially process it through a service layer, and store it using the repository pattern or domain logic. Ensure you handle temporary file cleanup and validation errors using HTTPException to maintain consistency with the global error handling in api/main.py.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →