8 Melhores práticas para criar aplicativos FastAPI e MongoDB
Avalie esse Artigo
FastAPI é uma estrutura web moderna e de alto desempenho para construir APIs com Python 3.8 ou posterior, com base em dicas de tipo. Seu design se concentra em codificação rápida e redução de erros, graças à validação automática do modelo de dados e a menos códigos padrão. O suporte do FastAPI para programação assíncrona garante que as APIs sejam eficientes e escaláveis, enquanto os recursos de documentação integrados, como o MongoDB UI e o ReDoc, fornecem ferramentas interativas de pesquisa da API.
O FastAPI se integra perfeitamente ao MongoDB por meio da biblioteca Motor, permitindo interações assíncronas com o banco de dados. Essa combinação oferece suporte a aplicativos escaláveis, aumentando a velocidade e a flexibilidade do tratamento de dados com o MongoDB. FastAPI e MongoDB juntos são ideais para criar aplicativos que gerenciam quantidades potencialmente grandes de dados complexos e diversos de forma eficiente. O MongoDB é um padronizadorsortudo do projeto FastAPI, então você pode perceber que é uma ótima opção para criar aplicativos com o MongoDB.
Todas as técnicas descritas neste artigo estão disponíveis no GitHub — confira o código-fonte! Com isso fora do caminho, agora podemos começar...
O FastAPI é particularmente adequado para criar APIs RESTful, em que solicitações de dados e atualizações no banco de dados são feitas usando solicitações HTTP, geralmente com cargas JSON. Mas a estrutura é igualmente excelente como back-end para sites HTML ou até mesmo aplicativos completos de página única (SPAs), onde a maioria das solicitações é feita via JavaScript. (Chamamos isso de pilha FARM — FastAPI, React, MongoDB — mas você pode trocar qualquer estrutura de componente front-end que desejar.) É particularmente flexível em relação ao back-end do banco de dados e à linguagem de modelo usada para renderizar HTML.
Na verdade, existem dois drivers Python para MongoDB – PyMongo e Motor – mas apenas um deles é adequado para uso com FastAPI. Como o FastAPI é construído sobre ASGI e asyncio, você precisa usar o Motor, que é compatível com asyncio. O PyMongo é apenas para aplicativos síncronos. Felizmente, assim como o PyMongo, o Motor é desenvolvido e totalmente suportado pelo MongoDB, então você pode confiar nele na produção, assim como faria com o PyMongo.
Você pode instalá-lo executando o seguinte comando em seu terminal (recomendo configurar um ambiente virtual Python primeiro!):
1 pip install motor[srv]
O extra
srv
inclui algumas dependências extras que são necessárias para a conexão com cadeias de conexão do MongoDB Atlas.Depois de instalado, você precisará usar o
AsyncIOMotorClient
no pacotemotor.motor_asyncio
.1 from fastapi import FastAPI 2 from motor.motor_asyncio import AsyncIOMotorClient 3 4 app = FastAPI() 5 6 # Load the MongoDB connection string from the environment variable MONGODB_URI 7 CONNECTION_STRING = os.environ['MONGODB_URI'] 8 9 # Create a MongoDB client 10 client = AsyncIOMotorClient(CONNECTION_STRING)
Observe que a cadeia de conexão não é armazenada no código! O que me leva a...
É muito fácil confirmar acidentalmente credenciais secretas em seu código e enviá-las para locais relativamente inseguros, como repositórios compartilhados do Git. Recomendo que você crieo hábito denunca colocar nenhum segredo em seu código.
Ao trabalhar com código, guardo meus segredos em um arquivo chamado
.envrc
— o conteúdo é carregado em variáveis de ambiente por uma ferramenta chamada direnv. Outras ferramentas para manter credenciais confidenciais fora do seu código incluem o envdir, uma biblioteca como o python-dotenv, e há várias ferramentas como oHoncho e oForeman. Você deve usar qualquer ferramenta que faça mais sentido para você. Se o arquivo que mantém seus segredos é chamado .env
ou .envrc
ou qualquer outra coisa, você deve adicionar esse nome de arquivo ao seu arquivogitignore global para que ele nunca seja adicionado a nenhum repositório.Na produção, você deve usar um KMS (sistema de gerenciamento de chaves) como o Vaultou talvez o KMS nativo na cloud de qualquer cloud que você esteja usando para hospedar seu aplicativo. Algumas pessoas até usam um KMS para gerenciar seus segredos em desenvolvimento.
Embora eu tenha inicializado minha conexão de banco de dados no código acima no nível superior de um pequeno aplicativo FastAPI, é uma prática recomendada inicializar e fechar normalmente a conexão do cliente, respondendo a eventos de inicialização e desligamento em seu aplicativo FastAPI. Você também deve anexar seu cliente ao objeto de aplicativo do FastAPI para disponibilizá-lo às suas funções de operação de caminho, onde quer que elas estejam em sua base de código. (Outras estruturas às vezes se referem a elas como “routes” ou “endpoints.”. A FastAPI as chama de “path operations.”) Se você confiar em uma variável global, precisará se preocupar em importá-la em todos os lugares necessários, o que pode ser confuso.
O trecho de código abaixo mostra como responder ao início e ao desligamento do seu aplicativo e como lidar com o cliente em resposta a cada um desses eventos:
1 from contextlib import asynccontextmanager 2 from logging import info @asynccontextmanager 3 async def db_lifespan(app: FastAPI): 4 # Startup 5 app.mongodb_client = AsyncIOMotorClient(CONNECTION_STRING) 6 app.database = app.mongodb_client.get_default_database() 7 ping_response = await app.database.command("ping") 8 if int(ping_response["ok"]) != 1: 9 raise Exception("Problem connecting to database cluster.") 10 else: 11 info("Connected to database cluster.") 12 13 yield 14 15 # Shutdown 16 app.mongodb_client.close() 17 18 19 app: FastAPI = FastAPI(lifespan=db_lifespan)
Um ODM, ou mapeador de documentos de objetos, é uma biblioteca que converte entre documentos e objetos em seu código. É em grande parte análogo a um ORM no mundo dos bancos de dados RDBMS. O uso de um ODM é um tópico complexo e, às vezes, ele pode ocultar coisas importantes, como a forma como os dados são armazenados e atualizados no banco de dados ou até mesmo alguns recursos avançados do MongoDB que você pode querer aproveitar. Seja qual for o ODM que você escolher, você deve examiná-lo altamente para ter certeza de que ele fará o que você deseja e crescerá com você.
Se você estiver escolhendo um ODM para seu aplicativo FastAPI, considere definitivamente o uso de um ODM baseado em Pydants , como ODMantic ouBeanie. A razão pela qual você deve preferir uma dessas bibliotecas é que a FastAPI foi construída com total integração com o Pydatatic. Isso quer Além de documentar sua interface, ele também fornece validação dos dados que você está retornando.
1 class Profile(Document): 2 """ 3 A profile for a single user as a Beanie Document. 4 5 Contains some useful information about a person. 6 """ 7 8 # Use a string for _id, instead of ObjectID: 9 id: Optional[str] = Field(default=None, description="MongoDB document ObjectID") 10 username: str 11 birthdate: datetime 12 website: List[str] 13 14 class Settings: 15 # The name of the collection to store these objects. 16 name = "profiles" 17 18 # A sample path operation to get a Profile: 19 20 async def get_profile(profile_id: str) -> Profile: 21 """ 22 Look up a single profile by ID. 23 """ 24 # This API endpoint demonstrates using Motor directly to look up a single 25 # profile by ID. 26 profile = await Profile.get(profile_id) 27 if profile is not None: 28 return profile 29 else: 30 raise HTTPException( 31 status_code=404, detail=f"No profile with id '{profile_id}'" 32 )
O objeto de perfil acima é documentado automaticamente no caminho "/docs":
Se você acha que trabalhar diretamente com o driver do Python MongoDB, Motor, faz mais sentido para você, posso lhe garantir que ele funciona muito bem para muitos aplicativos MongoDB grandes e complexos em produção. Se você ainda quiser os benefícios da documentação automatizada da API, poderá documentar seu esquema em seu código para que ele seja escolhido pelo FastAPI.
Como muitos aplicativos FastAPI incluem endpoints que fornecem dados JSON que são recuperados do MongoDB, é importante lembrar que determinados tipos que você pode armazenar em seu banco de dados, especialmente os tipos ObjectID e Binary, não existem no JSON. Felizmente, a FastAPI lida com datas e horários para você, codificando-os como strings formatadas.
Existem algumas maneiras diferentes de lidar com mapeamentos ObjectID. A primeira é evitá-los completamente usando um tipo compatível com JSON (como uma string) para valores de ID. Em muitos casos, isso não é prático, porque você já tem dados ou apenas porque o ObjectID é o tipo mais apropriado para sua chave primária. Nesse caso, você provavelmente desejará converter ObjectIDs em uma representação de string ao converter para JSON e fazer o inverso com dados que estão sendo enviados para seu aplicativo.
Se você estiver usando Beanie, ele assumirá automaticamente que o tipo do seu _id é um ObjectID e, portanto, definirá o tipo de campo como PydanticObjectId, que tratará automaticamente desse mapeamento de serialização para você. Você nem precisará declarar o id no seu modelo!
Se você especificar o tipo de resposta de suas operações de caminho, a FastAPI validará as respostas fornecidas e também filtrará todos os campos que não estejam definidos no tipo de resposta.
1 2 async def read_item(profile_id: str) -> Profile: 3 """ Use Beanie to look up a Profile. """ 4 profile = await Profile.get(profile_id) 5 return profile
Se você estiver usando o Motor, ainda poderá obter os benefícios de documentação, conversão, validação e filtragem retornando dados do documento, mas fornecendo o modelo Pydantic ao decorador:
1 2 3 4 5 async def read_item(profile_id: str) -> Mapping[str, Any]: 6 # This API endpoint demonstrates using Motor directly to look up a single 7 # profile by ID. 8 # 9 # It uses response_model (above) to tell FastAPI the schema of the data 10 # being returned, but it returns a dict directly, so that conversion and 11 # validation is done by FastAPI, meaning you don't have to copy values 12 # manually into a Profile before returning it. 13 profile = await app.profiles.find_one({"_id": profile_id}) 14 if profile is not None: 15 return profile
Um erro comum que as pessoas cometem ao construir servidores de API RESTful no MongoDB é armazenar os objetos de sua interface API exatamente da mesma maneira em seu MongoDB database. Isso pode funcionar muito bem em casos simples, especialmente se o aplicativo for uma API CRUD relativamente direta.
Em muitos casos, no entanto, você vai querer pensar sobre a melhor forma de modelar seus dados para atualizações e recuperação eficientes e ajudar a manter a integridade referencial e índices de tamanho razoável. Este é um tópico próprio, então, certifique-se de conferir a série de artigos sobre padrões de design no site do MongoDB e, talvez, considere fazer o curso online gratuito Advanced Schema Design Padrões na MongoDB University. (Existem muitos cursos gratuitos incríveis sobre diversos tópicos na MongoDB University.)
Se estiver trabalhando com um modelo de dados diferente no banco de dados e no aplicativo, será necessário mapear os valores recuperados do banco de dados e os valores fornecidos por meio de solicitações às operações do caminho da API. Separar o modelo físico do modelo de negócios tem a vantagem de permitir que você altere o esquema do banco de dados sem necessariamente alterar o esquema da API (e vice-versa).
Mesmo que você não esteja mapeando dados retornados do banco de dados (Ainda), fornecer uma classe Pydentic como
response_model
para sua operação de caminho converterá, validará, documentará e filtrará os campos dos dados BSON que você está retornando, então ele fornece muito valor! Aqui está um exemplo de uso desta técnica em um aplicativo FastAPI:1 # A Pydantic class modelling the *response* schema. 2 class Profile(BaseModel): 3 """ 4 A profile for a single user. 5 """ 6 id: Optional[str] = Field( 7 default=None, description="MongoDB document ObjectID", alias="_id" 8 ) 9 username: str 10 residence: str 11 current_location: List[float] 12 13 # A path operation that returns a Profile object as JSON: 14 15 16 17 18 async def get_profile(profile_id: str) -> Mapping[str, Any]: 19 # Uses response_model (above) to tell FastAPI the schema of the data 20 # being returned, but it returns a dict directly, so that conversion and 21 # validation is done by FastAPI, meaning you don't have to copy values 22 # manually into a Profile before returning it. 23 profile = await app.profiles.find_one({"_id": profile_id}) 24 if profile is not None: 25 return profile # Return BSON document (Mapping). Conversion etc will be done automatically. 26 else: 27 raise HTTPException( 28 status_code=404, detail=f"No profile with id '{profile_id}'" 29 )
Seus incríveis colaboradores criaram um gerador de aplicativos para fazer muitas dessas coisas por você e ajudá-lo a entrar em operação o mais rápido possível com um serviço FastAPI, React e MongoDB dockerizado e de qualidade de produção, apoiado por testes e integração contínua. Você pode conferir no repositório do Full-Stack FastAPI MongoDB no Github .
Esperemos que este artigo tenha fornecido a você o conhecimento para criar um aplicativo FastAPI que seja dimensionado tanto para a quantidade de código que você escreve quanto para os dados que você precisa armazenar. Estamos sempre procurando por mais truques e dicas para criar aplicativos com FastAPI e MongoDB, então, se você tiver algumas sugestões, por que não abrir um ticket no Github e conversamos?
Gostamos de saber o que você está construindo com a FastAPI ou qualquer outra framework — seja um projeto de passatempo ou um aplicativo empresarial que vai mudar o mundo. Conte para nós o que você está construindo nos MongoDB Community. Também é um ótimo lugar para passar se você estiver com problemas — alguém nos fóruns provavelmente pode ajudá-lo!
Principais comentários nos fóruns
Alessio_RuggiAlessio Ruggilast quarter
Estou comentando apenas para apontar que o link para o Github parece não funcionar