initial commit
This commit is contained in:
594
docs/getting-started/first-run.md
Normal file
594
docs/getting-started/first-run.md
Normal file
@ -0,0 +1,594 @@
|
||||
# First Run Guide
|
||||
|
||||
Congratulations on setting up the FastAPI Boilerplate! This guide will walk you through testing your installation, understanding the basics, and making your first customizations.
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
Before diving deeper, let's verify everything is working correctly.
|
||||
|
||||
### 1. Check All Services
|
||||
|
||||
Ensure all services are running:
|
||||
|
||||
```bash
|
||||
# For Docker Compose users
|
||||
docker compose ps
|
||||
|
||||
# Expected output:
|
||||
# NAME COMMAND SERVICE STATUS
|
||||
# fastapi-boilerplate-web-1 "uvicorn app.main:app…" web running
|
||||
# fastapi-boilerplate-db-1 "docker-entrypoint.s…" db running
|
||||
# fastapi-boilerplate-redis-1 "docker-entrypoint.s…" redis running
|
||||
# fastapi-boilerplate-worker-1 "arq src.app.core.wo…" worker running
|
||||
```
|
||||
|
||||
### 2. Test API Endpoints
|
||||
|
||||
Visit these URLs to confirm your API is working:
|
||||
|
||||
**API Documentation:**
|
||||
- **Swagger UI**: [http://localhost:8000/docs](http://localhost:8000/docs)
|
||||
- **ReDoc**: [http://localhost:8000/redoc](http://localhost:8000/redoc)
|
||||
|
||||
**Health Check:**
|
||||
```bash
|
||||
curl http://localhost:8000/api/v1/health
|
||||
```
|
||||
|
||||
Expected response:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2024-01-01T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Database Connection
|
||||
|
||||
Check if the database tables were created:
|
||||
|
||||
```bash
|
||||
# For Docker Compose
|
||||
docker compose exec db psql -U postgres -d myapp -c "\dt"
|
||||
|
||||
# You should see tables like:
|
||||
# public | users | table | postgres
|
||||
# public | posts | table | postgres
|
||||
# public | tiers | table | postgres
|
||||
# public | rate_limits | table | postgres
|
||||
```
|
||||
|
||||
### 4. Redis Connection
|
||||
|
||||
Test Redis connectivity:
|
||||
|
||||
```bash
|
||||
# For Docker Compose
|
||||
docker compose exec redis redis-cli ping
|
||||
|
||||
# Expected response: PONG
|
||||
```
|
||||
|
||||
## Initial Setup
|
||||
|
||||
Before testing features, you need to create the first superuser and tier.
|
||||
|
||||
### Creating the First Superuser
|
||||
|
||||
!!! warning "Prerequisites"
|
||||
Make sure the database and tables are created before running create_superuser. The database should be running and the API should have started at least once.
|
||||
|
||||
#### Using Docker Compose
|
||||
|
||||
If using Docker Compose, uncomment this section in your `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
#-------- uncomment to create first superuser --------
|
||||
create_superuser:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
env_file:
|
||||
- ./src/.env
|
||||
depends_on:
|
||||
- db
|
||||
command: python -m src.scripts.create_first_superuser
|
||||
volumes:
|
||||
- ./src:/code/src
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
# Start services and run create_superuser automatically
|
||||
docker compose up -d
|
||||
|
||||
# Or run it manually
|
||||
docker compose run --rm create_superuser
|
||||
|
||||
# Stop the create_superuser service when done
|
||||
docker compose stop create_superuser
|
||||
```
|
||||
|
||||
#### From Scratch
|
||||
|
||||
If running manually, use:
|
||||
|
||||
```bash
|
||||
# Make sure you're in the root folder
|
||||
uv run python -m src.scripts.create_first_superuser
|
||||
```
|
||||
|
||||
### Creating the First Tier
|
||||
|
||||
!!! warning "Prerequisites"
|
||||
Make sure the database and tables are created before running create_tier.
|
||||
|
||||
#### Using Docker Compose
|
||||
|
||||
Uncomment the `create_tier` service in `docker-compose.yml` and run:
|
||||
|
||||
```bash
|
||||
docker compose run --rm create_tier
|
||||
```
|
||||
|
||||
#### From Scratch
|
||||
|
||||
```bash
|
||||
# Make sure you're in the root folder
|
||||
uv run python -m src.scripts.create_first_tier
|
||||
```
|
||||
|
||||
## Testing Core Features
|
||||
|
||||
Let's test the main features of your API.
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
#### 1. Login with Admin User
|
||||
|
||||
Use the admin credentials you set in your `.env` file:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/api/v1/login" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "username=admin&password=your_admin_password"
|
||||
```
|
||||
|
||||
You should receive a response like:
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
||||
"token_type": "bearer",
|
||||
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Create a New User
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/api/v1/users" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "John Doe",
|
||||
"username": "johndoe",
|
||||
"email": "john@example.com",
|
||||
"password": "securepassword123"
|
||||
}'
|
||||
```
|
||||
|
||||
#### 3. Test Protected Endpoint
|
||||
|
||||
Use the access token from step 1:
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/api/v1/users/me" \
|
||||
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE"
|
||||
```
|
||||
|
||||
### CRUD Operations
|
||||
|
||||
#### 1. Create a Post
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/api/v1/posts" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE" \
|
||||
-d '{
|
||||
"title": "My First Post",
|
||||
"content": "This is the content of my first post!"
|
||||
}'
|
||||
```
|
||||
|
||||
#### 2. Get All Posts
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/api/v1/posts" \
|
||||
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE"
|
||||
```
|
||||
|
||||
#### 3. Get Posts with Pagination
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/api/v1/posts?page=1&items_per_page=5" \
|
||||
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE"
|
||||
```
|
||||
|
||||
### Background Tasks
|
||||
|
||||
Test the job queue system:
|
||||
|
||||
#### 1. Submit a Background Task
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/api/v1/tasks/task?message=hello" \
|
||||
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Check Task Status
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/api/v1/tasks/task/550e8400-e29b-41d4-a716-446655440000" \
|
||||
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE"
|
||||
```
|
||||
|
||||
### Caching
|
||||
|
||||
Test the caching system:
|
||||
|
||||
#### 1. Make a Cached Request
|
||||
|
||||
```bash
|
||||
# First request (cache miss)
|
||||
curl -X GET "http://localhost:8000/api/v1/users/johndoe" \
|
||||
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE" \
|
||||
-w "Time: %{time_total}s\n"
|
||||
|
||||
# Second request (cache hit - should be faster)
|
||||
curl -X GET "http://localhost:8000/api/v1/users/johndoe" \
|
||||
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE" \
|
||||
-w "Time: %{time_total}s\n"
|
||||
```
|
||||
|
||||
## Your First Customization
|
||||
|
||||
Let's create a simple custom endpoint to see how easy it is to extend the boilerplate.
|
||||
|
||||
### 1. Create a Simple Model
|
||||
|
||||
Create `src/app/models/item.py`:
|
||||
|
||||
```python
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.core.db.database import Base
|
||||
|
||||
|
||||
class Item(Base):
|
||||
__tablename__ = "items"
|
||||
|
||||
id: Mapped[int] = mapped_column("id", autoincrement=True, nullable=False, unique=True, primary_key=True, init=False)
|
||||
name: Mapped[str] = mapped_column(String(100))
|
||||
description: Mapped[str] = mapped_column(String(500), default="")
|
||||
```
|
||||
|
||||
### 2. Create Pydantic Schemas
|
||||
|
||||
Create `src/app/schemas/item.py`:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ItemBase(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=100)
|
||||
description: str = Field("", max_length=500)
|
||||
|
||||
|
||||
class ItemCreate(ItemBase):
|
||||
pass
|
||||
|
||||
|
||||
class ItemCreateInternal(ItemCreate):
|
||||
pass
|
||||
|
||||
|
||||
class ItemRead(ItemBase):
|
||||
id: int
|
||||
|
||||
|
||||
class ItemUpdate(BaseModel):
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
|
||||
|
||||
class ItemUpdateInternal(ItemUpdate):
|
||||
pass
|
||||
|
||||
|
||||
class ItemDelete(BaseModel):
|
||||
is_deleted: bool = True
|
||||
```
|
||||
|
||||
### 3. Create CRUD Operations
|
||||
|
||||
Create `src/app/crud/crud_items.py`:
|
||||
|
||||
```python
|
||||
from fastcrud import FastCRUD
|
||||
|
||||
from app.models.item import Item
|
||||
from app.schemas.item import ItemCreateInternal, ItemUpdate, ItemUpdateInternal, ItemDelete
|
||||
|
||||
CRUDItem = FastCRUD[Item, ItemCreateInternal, ItemUpdate, ItemUpdateInternal, ItemDelete]
|
||||
crud_items = CRUDItem(Item)
|
||||
```
|
||||
|
||||
### 4. Create API Endpoints
|
||||
|
||||
Create `src/app/api/v1/items.py`:
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.dependencies import get_current_user
|
||||
from app.core.db.database import async_get_db
|
||||
from app.crud.crud_items import crud_items
|
||||
from app.schemas.item import ItemCreate, ItemRead, ItemUpdate
|
||||
from app.schemas.user import UserRead
|
||||
|
||||
router = APIRouter(tags=["items"])
|
||||
|
||||
|
||||
@router.post("/", response_model=ItemRead, status_code=201)
|
||||
async def create_item(
|
||||
item: ItemCreate,
|
||||
db: Annotated[AsyncSession, Depends(async_get_db)],
|
||||
current_user: Annotated[UserRead, Depends(get_current_user)]
|
||||
):
|
||||
"""Create a new item."""
|
||||
db_item = await crud_items.create(db=db, object=item)
|
||||
return db_item
|
||||
|
||||
|
||||
@router.get("/{item_id}", response_model=ItemRead)
|
||||
async def get_item(
|
||||
item_id: int,
|
||||
db: Annotated[AsyncSession, Depends(async_get_db)]
|
||||
):
|
||||
"""Get an item by ID."""
|
||||
db_item = await crud_items.get(db=db, id=item_id)
|
||||
if not db_item:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
return db_item
|
||||
|
||||
|
||||
@router.get("/", response_model=list[ItemRead])
|
||||
async def get_items(
|
||||
db: Annotated[AsyncSession, Depends(async_get_db)],
|
||||
skip: int = 0,
|
||||
limit: int = 100
|
||||
):
|
||||
"""Get all items."""
|
||||
items = await crud_items.get_multi(db=db, offset=skip, limit=limit)
|
||||
return items["data"]
|
||||
|
||||
|
||||
@router.patch("/{item_id}", response_model=ItemRead)
|
||||
async def update_item(
|
||||
item_id: int,
|
||||
item_update: ItemUpdate,
|
||||
db: Annotated[AsyncSession, Depends(async_get_db)],
|
||||
current_user: Annotated[UserRead, Depends(get_current_user)]
|
||||
):
|
||||
"""Update an item."""
|
||||
db_item = await crud_items.get(db=db, id=item_id)
|
||||
if not db_item:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
|
||||
updated_item = await crud_items.update(db=db, object=item_update, id=item_id)
|
||||
return updated_item
|
||||
|
||||
|
||||
@router.delete("/{item_id}")
|
||||
async def delete_item(
|
||||
item_id: int,
|
||||
db: Annotated[AsyncSession, Depends(async_get_db)],
|
||||
current_user: Annotated[UserRead, Depends(get_current_user)]
|
||||
):
|
||||
"""Delete an item."""
|
||||
db_item = await crud_items.get(db=db, id=item_id)
|
||||
if not db_item:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
|
||||
await crud_items.delete(db=db, id=item_id)
|
||||
return {"message": "Item deleted successfully"}
|
||||
```
|
||||
|
||||
### 5. Register the Router
|
||||
|
||||
Add your new router to `src/app/api/v1/__init__.py`:
|
||||
|
||||
```python
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.v1.login import router as login_router
|
||||
from app.api.v1.logout import router as logout_router
|
||||
from app.api.v1.posts import router as posts_router
|
||||
from app.api.v1.rate_limits import router as rate_limits_router
|
||||
from app.api.v1.tasks import router as tasks_router
|
||||
from app.api.v1.tiers import router as tiers_router
|
||||
from app.api.v1.users import router as users_router
|
||||
from app.api.v1.items import router as items_router # Add this line
|
||||
|
||||
router = APIRouter(prefix="/v1")
|
||||
router.include_router(login_router, prefix="/login")
|
||||
router.include_router(logout_router, prefix="/logout")
|
||||
router.include_router(users_router, prefix="/users")
|
||||
router.include_router(posts_router, prefix="/posts")
|
||||
router.include_router(tasks_router, prefix="/tasks")
|
||||
router.include_router(tiers_router, prefix="/tiers")
|
||||
router.include_router(rate_limits_router, prefix="/rate_limits")
|
||||
router.include_router(items_router, prefix="/items") # Add this line
|
||||
```
|
||||
|
||||
### 6. Create and Run Migration
|
||||
|
||||
Import your new model in `src/app/models/__init__.py`:
|
||||
|
||||
```python
|
||||
from .user import User
|
||||
from .post import Post
|
||||
from .tier import Tier
|
||||
from .rate_limit import RateLimit
|
||||
from .item import Item # Add this line
|
||||
```
|
||||
|
||||
Create and run the migration:
|
||||
|
||||
```bash
|
||||
# For Docker Compose
|
||||
docker compose exec web alembic revision --autogenerate -m "Add items table"
|
||||
docker compose exec web alembic upgrade head
|
||||
|
||||
# For manual installation
|
||||
cd src
|
||||
uv run alembic revision --autogenerate -m "Add items table"
|
||||
uv run alembic upgrade head
|
||||
```
|
||||
|
||||
### 7. Test Your New Endpoint
|
||||
|
||||
Restart your application and test the new endpoints:
|
||||
|
||||
```bash
|
||||
# Create an item
|
||||
curl -X POST "http://localhost:8000/api/v1/items/" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE" \
|
||||
-d '{
|
||||
"name": "My First Item",
|
||||
"description": "This is a test item"
|
||||
}'
|
||||
|
||||
# Get all items
|
||||
curl -X GET "http://localhost:8000/api/v1/items/" \
|
||||
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE"
|
||||
```
|
||||
|
||||
## Debugging Common Issues
|
||||
|
||||
### Logs and Monitoring
|
||||
|
||||
#### Check Application Logs
|
||||
|
||||
```bash
|
||||
# For Docker Compose
|
||||
docker compose logs web
|
||||
|
||||
# For manual installation
|
||||
tail -f src/app/logs/app.log
|
||||
```
|
||||
|
||||
#### Check Database Logs
|
||||
|
||||
```bash
|
||||
# For Docker Compose
|
||||
docker compose logs db
|
||||
```
|
||||
|
||||
#### Check Worker Logs
|
||||
|
||||
```bash
|
||||
# For Docker Compose
|
||||
docker compose logs worker
|
||||
```
|
||||
|
||||
### Performance Testing
|
||||
|
||||
#### Test API Response Times
|
||||
|
||||
```bash
|
||||
# Test endpoint performance
|
||||
curl -w "Time: %{time_total}s\n" \
|
||||
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE" \
|
||||
http://localhost:8000/api/v1/users/me
|
||||
```
|
||||
|
||||
#### Test Database Performance
|
||||
|
||||
```bash
|
||||
# Check active connections
|
||||
docker compose exec db psql -U postgres -d myapp -c "SELECT count(*) FROM pg_stat_activity;"
|
||||
```
|
||||
|
||||
## Monitoring Dashboard
|
||||
|
||||
### Redis Monitor
|
||||
|
||||
```bash
|
||||
# Monitor Redis operations
|
||||
docker compose exec redis redis-cli monitor
|
||||
```
|
||||
|
||||
### Database Activity
|
||||
|
||||
```bash
|
||||
# Check database activity
|
||||
docker compose exec db psql -U postgres -d myapp -c "SELECT * FROM pg_stat_activity;"
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you've verified everything works and created your first custom endpoint, you're ready to dive deeper:
|
||||
|
||||
### Essential Learning
|
||||
|
||||
1. **[Project Structure](../user-guide/project-structure.md)** - Understand how the code is organized
|
||||
2. **[Database Guide](../user-guide/database/index.md)** - Learn about models, schemas, and CRUD operations
|
||||
3. **[Authentication](../user-guide/authentication/index.md)** - Deep dive into JWT and user management
|
||||
|
||||
### Advanced Features
|
||||
|
||||
1. **[Caching](../user-guide/caching/index.md)** - Speed up your API with Redis caching
|
||||
2. **[Background Tasks](../user-guide/background-tasks/index.md)** - Process long-running tasks asynchronously
|
||||
3. **[Rate Limiting](../user-guide/rate-limiting/index.md)** - Protect your API from abuse
|
||||
|
||||
### Development Workflow
|
||||
|
||||
1. **[Development Guide](../user-guide/development.md)** - Best practices for extending the boilerplate
|
||||
2. **[Testing](../user-guide/testing.md)** - Write tests for your new features
|
||||
3. **[Production](../user-guide/production.md)** - Deploy your API to production
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter any issues:
|
||||
|
||||
1. **Check the logs** for error messages
|
||||
2. **Verify your configuration** in the `.env` file
|
||||
3. **Review the [GitHub Issues](https://github.com/benavlabs/fastapi-boilerplate/issues)** for common solutions
|
||||
4. **Search [existing issues](https://github.com/benavlabs/fastapi-boilerplate/issues)** on GitHub
|
||||
5. **Create a [new issue](https://github.com/benavlabs/fastapi-boilerplate/issues/new)** with detailed information
|
||||
|
||||
## Congratulations!
|
||||
|
||||
You've successfully:
|
||||
|
||||
- Verified your FastAPI Boilerplate installation
|
||||
- Tested core API functionality
|
||||
- Created your first custom endpoint
|
||||
- Run database migrations
|
||||
- Tested authentication and CRUD operations
|
||||
|
||||
You're now ready to build amazing APIs with FastAPI!
|
||||
Reference in New Issue
Block a user