Pular para o conteúdo
Desenvolvimento

Do zero ao deploy: crie uma API de cadastro com FastAPI e Docker

Admin5 min de leitura
Do zero ao deploy: crie uma API de cadastro com FastAPI e Docker

Do zero ao deploy: crie uma API de cadastro com FastAPI e Docker

Objetivo: Demonstrar, de forma prática, como iniciar um projeto do zero, escrever o código da aplicação, configurá‑la para rodar em containers e deixá‑la pronta para produção.

Tecnologias usadas: Python 3.11, FastAPI, SQLAlchemy, PostgreSQL, Docker, Docker‑Compose.

Tecnologia e Inovação

Introdução

A criação de um serviço web do início ao fim costuma envolver diversas etapas: definição da arquitetura, escolha de linguagens, configuração de banco de dados, provisionamento de ambiente e, por fim, o deploy. Para quem está começando ou para equipes que desejam um ponto de partida sólido, um tutorial hands‑on que mostre tudo em um único fluxo pode economizar horas de pesquisa.

Neste artigo, vamos construir uma API simples de cadastro de usuários. O foco está em:

  • Estrutura de pastas que favorece a manutenção.
  • Código funcional – todo exemplo pode ser copiado e executado imediatamente.
  • Ambiente containerizado com Docker e Docker‑Compose, garantindo que todos os desenvolvedores trabalhem nas mesmas condições.
  • Procedimentos de teste local e preparação para o deploy em um servidor ou serviço de nuvem.
  • Ao final, você terá um repositório pronto para ser versionado e ampliado com novas funcionalidades.


    1. Preparando o ambiente

    1.1. Instalando Docker

    Docker é a ferramenta que permite empacotar a aplicação e todas as suas dependências em containers isolados. Se ainda não o tem instalado, siga a documentação oficial:

    • Linux: sudo apt-get install docker.io docker-compose
    • macOS / Windows: baixe o Docker Desktop em .

    Dica: Verifique a instalação com docker version e docker-compose version.

    1.2. Estrutura inicial de diretórios

    Crie uma pasta para o projeto e, dentro dela, a seguinte hierarquia:

    fastapi-docker-demo/
    

    ├─ app/ │ ├─ __init__.py │ ├─ main.py │ ├─ models.py │ ├─ schemas.py │ └─ crud.py ├─ tests/ │ └─ test_api.py ├─ Dockerfile ├─ docker-compose.yml └─ requirements.txt

    Essa separação deixa claro onde está a lógica de negócios (crud.py), as definições de banco (models.py) e os contratos de entrada/saída (schemas.py).

    1.3. Definindo dependências

    No arquivo requirements.txt adicione:

    fastapi==0.110.0
    

    uvicorn[standard]==0.27.0 SQLAlchemy==2.0.23 psycopg2-binary==2.9.9 pydantic==2.5.2

    Essas versões são as mais recentes no momento da escrita e garantem compatibilidade entre si.


    2. Estruturando o projeto

    2.1. Modelo de dados (app/models.py)

    # app/models.py
    

    from sqlalchemy import Column, Integer, String, Boolean, DateTime from sqlalchemy.ext.declarative import declarative_base from datetime import datetime

    Base = declarative_base()

    class User(Base): __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True) username = Column(String(50), unique=True, nullable=False, index=True) full_name = Column(String(120), nullable=False) email = Column(String(120), unique=True, nullable=False, index=True) is_active = Column(Boolean, default=True) created_at = Column(DateTime, default=datetime.utcnow)

    O modelo segue o padrão SQLAlchemy, com índices nas colunas de busca frequente (username, email).

    2.2. Schemas de entrada/saída (app/schemas.py)

    # app/schemas.py
    

    from pydantic import BaseModel, EmailStr, Field from datetime import datetime

    class UserBase(BaseModel): username: str = Field(..., min_length=3, max_length=50) full_name: str = Field(..., max_length=120) email: EmailStr

    class UserCreate(UserBase): password: str = Field(..., min_length=6)

    class UserRead(UserBase): id: int is_active: bool created_at: datetime

    class Config: orm_mode = True

    Usamos pydantic para validação automática e para transformar objetos SQLAlchemy em respostas JSON.

    2.3. Operações CRUD (app/crud.py)

    # app/crud.py
    

    from sqlalchemy.orm import Session from . import models, schemas from passlib.context import CryptContext

    pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

    def get_password_hash(password: str) -> str: return pwd_context.hash(password)

    def create_user(db: Session, user_in: schemas.UserCreate) -> models.User: hashed_pw = get_password_hash(user_in.password) db_user = models.User( username=user_in.username, full_name=user_in.full_name, email=user_in.email, ) # Armazenamos a senha em campo separado (não mostrado aqui) db.add(db_user) db.commit() db.refresh(db_user) return db_user

    def get_user_by_username(db: Session, username: str): return db.query(models.User).filter(models.User.username == username).first()

    def list_users(db: Session, skip: int = 0, limit: int = 100): return db.query(models.User).offset(skip).limit(limit).all()

    A camada crud mantém a lógica de acesso ao banco isolada da camada HTTP.

    2.4. Aplicação FastAPI (app/main.py)

    # app/main.py
    

    from fastapi import FastAPI, Depends, HTTPException, status from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, Session from . import models, schemas, crud

    DATABASE_URL = "postgresql://postgres:postgres@db:5432/appdb"

    engine = create_engine(DATABASE_URL, echo=False) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

    Cria as tabelas caso ainda não existam

    models.Base.metadata.create_all(bind=engine)

    app = FastAPI(title="Cadastro de Usuários")

    def get_db(): db = SessionLocal() try: yield db finally: db.close()

    @app.post("/users/", response_model=schemas.UserRead, status_code=status.HTTP_201_CREATED) def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): db_user = crud.get_user_by_username(db, user.username) if db_user: raise HTTPException(status_code=400, detail="Nome de usuário já existe") return crud.create_user(db, user)

    @app.get("/users/", response_model=list[schemas.UserRead]) def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)): users = crud.list_users(db, skip=skip, limit=limit) return users

    Observe que a string de conexão aponta para o serviço db definido no docker‑compose.yml. Isso permite que a aplicação encontre o banco mesmo quando estiver dentro de um container.


    3. Containerizando a aplicação

    3.1. Dockerfile

    # Dockerfile
    

    FROM python:3.11-slim

    Variáveis de ambiente para melhorar a performance do Python dentro do container

    ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1

    Diretório de trabalho

    WORKDIR /app

    Copia apenas arquivos de dependência para aproveitar cache de camadas

    COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt

    Copia o código da aplicação

    COPY app ./app

    Exponha a porta padrão do uvicorn

    EXPOSE 8000

    Comando padrão

    CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

    Esse Dockerfile cria uma imagem mínima, baseada em python:3.11-slim, instala apenas as dependências e copia o código da aplicação.

    3.2. Docker‑Compose (docker-compose.yml)

    # docker-compose.yml
    

    version: "3.9"

    services: api: build: . container_name: fastapi_api ports: - "8000:8000" depends_on: - db environment: - PYTHONUNBUFFERED=1 restart: unless-stopped

    db: image: postgres:15-alpine container_name: fastapi_db environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: appdb volumes: - pgdata:/var/lib/postgresql/data ports: - "5432:5432" restart: unless-stopped

    volumes: pgdata:

    • api: compila a imagem a partir do Dockerfile e expõe a porta 8000.
    • db: roda o PostgreSQL oficial, com credenciais simples para desenvolvimento.

    Importante: Em produção, nunca use credenciais padrão. Substitua por variáveis de ambiente seguras ou secret managers.

    3.3. Levando tudo ao ar

    # Na raiz do projeto
    

    docker-compose up --build

    O Docker criará duas imagens (Python + dependências, PostgreSQL) e iniciará os containers. Quando o log indicar algo como `U

    Artigos relacionados