Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
Pythonchevron-right

Apresentando FARM Stack - FastAPI, React e MongoDB

Aaron Bassett7 min read • Published Feb 05, 2022 • Updated Apr 02, 2024
FastAPIJavaScriptPython
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty
{Quando consegui minha primeira tarefa na área de programação, a pilha LAMP (Linux, Apache, MySQL, PHP)e suas variações- reinava suprema. Eu usava o WAMP no trabalho, o DAMP em casa e implementava nossos clientes no SAMP. Mas agora todas as pilhas com acrônimos memoráveis parecem ser muito avançadas em termos de JavaScript. MEAN (MongoDB, Express, Angular, Node.js), MERN (MongoDB, Express, React, Node.js), MEVN (MongoDB, Express, Vue, Node.js), JAM (JavaScript, APIs, Markup) e assim por diante.
Por mais que eu curta trabalhar com React e Vue, o Python ainda é minha linguagem favorita para criar web services de back-end. Eu queria os mesmos benefícios que obtive do MERN - MongoDB, velocidade, flexibilidade, código padrão mínimo - mas com Python em vez de Node.js. Com isso em mente, gostaria de apresentar a pilha FARM; FastAPI, React e MongoDB.

O que é FastAPI?

A pilha FARM é, em muitos aspectos, muito semelhante ao MERN. Mantivemos o MongoDB e o React, mas substituímos o back-end do Node.js e do Express por Python e FastAPI. FastAPI é um framework moderno e de alto desempenho em Python 3.6+ web. No que diz respeito aos frameworks web, é incrivelmente novo. O commit mais antigo do Git que eu consegui encontrar é de 5 de dezembro de 2018, mas é uma estrela em ascensão na comunidade Python. Ele já é usado em produção por empresas como Microsoft, Uber e Netflix.
E é rápido. Os benchmarks mostram que não é tão rápido quanto o Chi do Golang ou fasthttp, mas é mais rápido do que todos os outros frameworks Python testados e supera a maioria dos Node.js também.

Comece a usar

Se você quiser experimentar a pilha FARM, criei um exemplo de aplicativo TODO que você pode clonar no GitHub.
1git clone git@github.com:mongodb-developer/FARM-Intro.git
O código é organizado em dois diretórios: back-end e front-end. O código de back-end é o nosso servidor FastAPI. O código nesse diretório interage com nosso MongoDB database, cria nossos endpoints de API e, graças ao OAS3 (Especificação OpenAPI 3). Ele também gera nossa documentação interativa.

Executando o servidor FastAPI

Antes de eu analisar o código, tente executar o servidor FastAPI. Você precisará de Python 3.8+ e um MongoDB database. Um Atlas Cluster gratuito será mais que suficiente. Anote seu nome de usuário, senha e string de conexão do MongoDB, pois você precisará deles em breve.

Instalando dependências

1cd FARM-Intro/backend
2pip install -r requirements.txt

Configurando variáveis de ambiente

1export DEBUG_MODE=True
2export DB_URL="mongodb+srv://<username>:<password>@<url>/<db>?retryWrites=true&w=majority"
3export DB_NAME="farmstack"
Depois de ter tudo instalado e configurado, você pode executar o servidor com python main.py e acessar http://localhost:8000/docs no seu navegador.
Screencast de operações CRUD por meio de documentos FastAPI
Essa documentação interativa é gerada automaticamente para nós pela FastAPI e é uma ótima maneira de experimentar sua API durante o desenvolvimento. Você pode ver que cobrimos os principais elementos do CRUD. Tente adicionar, atualizar e excluir algumas tarefas e explore as respostas que você recebe do servidor FastAPI.

Criando um servidor FastAPI

Inicializamos o servidor em main.py; é aqui que criamos nosso aplicativo.
1app = FastAPI()
Anexe nossas rotas ou endpoints de API.
1app.include_router(todo_router, tags=["tasks"], prefix="/task")
Inicie o loop de eventos assíncronos e o servidor ASGI.
1if __name__ == "__main__":
2 uvicorn.run(
3 "main:app",
4 host=settings.HOST,
5 reload=settings.DEBUG_MODE,
6 port=settings.PORT,
7 )
E é também onde abrimos e fechamos a conexão com nosso servidor MongoDB.
1@app.on_event("startup")
2async def startup_db_client():
3 app.mongodb_client = AsyncIOMotorClient(settings.DB_URL)
4 app.mongodb = app.mongodb_client[settings.DB_NAME]
5
6
7@app.on_event("shutdown")
8async def shutdown_db_client():
9 app.mongodb_client.close()
Como a FastAPI é um framework assíncrono, estamos usando o Motor para nos conectar ao nosso servidor MongoDB. Motor é o driver Python assíncrono oficialmente mantido para o MongoDB.
Quando o evento de inicialização do aplicativo é acionado, abro uma conexão com o MongoDB e garanto que ele esteja disponível por meio do objeto do aplicativo para que eu possa acessá-lo posteriormente em meus diferentes roteadores.

Definindo modelos

Muitas pessoas pensam que o MongoDB não tem esquema, o que está errado. O MongoDB tem um esquema flexível. Ou seja, as coleções não impõem a estrutura do documento por padrão, então você tem a flexibilidade de fazer as escolhas de modelagem de dados que melhor atendem ao seu aplicativo e seus requisitos de desempenho. Portanto, não é incomum criar modelos ao trabalhar com um MongoDB database.
Os modelos do aplicativo TODO estão em backend/apps/todo/models.py, e são esses modelos que ajudam a FastAPI a criar a documentação interativa.
1class TaskModel(BaseModel):
2 id: str = Field(default_factory=uuid.uuid4, alias="_id")
3 name: str = Field(...)
4 completed: bool = False
5
6 class Config:
7 allow_population_by_field_name = True
8 schema_extra = {
9 "example": {
10 "id": "00010203-0405-0607-0809-0a0b0c0d0e0f",
11 "name": "My important task",
12 "completed": True,
13 }
14 }
Quero chamar a atenção para o campo id neste modelo. O MongoDB usa _id, mas no Python, os sublinhados no início dos atributos têm um significado especial. Se você tiver um atributo em seu modelo que comece com um sublinhado, pydentic – a estrutura de validação de dados usada pelo FastAPI – assumirá que é uma variável privada, o que significa que você não poderá atribuir um valor a ele! Para contornar isso, nomeamos o campo id, mas damos a ele um alias de _id. Você também precisa definir allow_population_by_field_name como True na classe Config do modelo.
Você pode notar que não estou usando os ObjectIds do MongoDB. Você pode usar ObjectIds com FastAPI; há apenas mais trabalho necessário durante a serialização e a desserialização. Ainda assim, para este exemplo, acho mais fácil gerar os UUIDs, então eles são sempre strings.
1class UpdateTaskModel(BaseModel):
2 name: Optional[str]
3 completed: Optional[bool]
4
5 class Config:
6 schema_extra = {
7 "example": {
8 "name": "My important task",
9 "completed": True,
10 }
11 }
Quando os usuários estiverem atualizando tarefas, não queremos que eles alterem o id, portanto, o UpdateTaskModel inclui apenas os campos nome e concluído. Também tornei ambos os campos opcionais para que você possa atualizar qualquer um deles independentemente. Tornar ambos opcionais significou que todos os campos eram opcionais, o que me fez perder muito tempo decidindo como lidar com uma solicitação PUT (uma atualização) em que o usuário não enviou nenhum campo para ser alterado. Veremos isso a seguir quando analisarmos os roteadores.

Roteadores FastAPI

Os roteadores de tarefas estão dentro de backend/apps/todo/routers.py.
Para cobrir as diferentes operações CRUD (Criar, Ler, Atualizar e Excluir, em português), precisei dos seguintes endpoints:
  • POST /task/ - cria uma nova tarefa.
  • GET /task/ - visualizar todas as tarefas existentes.
  • GET /task/{id}/ - visualizar uma única tarefa.
  • PUT /task/{id}/ - atualizar uma tarefa.
  • DELETE /task/{id}/ - excluir uma tarefa.

criar

1@router.post("/", response_description="Add new task")
2async def create_task(request: Request, task: TaskModel = Body(...)):
3 task = jsonable_encoder(task)
4 new_task = await request.app.mongodb["tasks"].insert_one(task)
5 created_task = await request.app.mongodb["tasks"].find_one(
6 {"_id": new_task.inserted_id}
7 )
8
9 return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_task)
O roteador create_task aceita os novos dados da tarefa no corpo da solicitação como uma string JSON. Gravamos esses dados no MongoDB e, em seguida, respondemos com um status de 201 HTTP e a tarefa recém-criada.

Leia

1@router.get("/", response_description="List all tasks")
2async def list_tasks(request: Request):
3 tasks = []
4 for doc in await request.app.mongodb["tasks"].find().to_list(length=100):
5 tasks.append(doc)
6 return tasks
O roteador list_tasks é excessivamente simplista. Em um aplicativo do mundo real, você precisará, no mínimo, incluir a paginação. Felizmente, existem pacotes para a FastAPI que podem simplificar esse processo.
1@router.get("/{id}", response_description="Get a single task")
2async def show_task(id: str, request: Request):
3 if (task := await request.app.mongodb["tasks"].find_one({"_id": id})) is not None:
4 return task
5
6 raise HTTPException(status_code=404, detail=f"Task {id} not found")
Embora a FastAPI seja compatível com Python 3.6+, é o meu uso de expressões de atribuição em roteadores como esse, e é por isso que esse aplicativo de exemplo exige Python 3.8+.
Aqui, estou criando uma exceção se não conseguirmos encontrar uma tarefa com o ID correto.

Atualização

1@router.put("/{id}", response_description="Update a task")
2async def update_task(id: str, request: Request, task: UpdateTaskModel = Body(...)):
3 task = {k: v for k, v in task.dict().items() if v is not None}
4
5 if len(task) >= 1:
6 update_result = await request.app.mongodb["tasks"].update_one(
7 {"_id": id}, {"$set": task}
8 )
9
10 if update_result.modified_count == 1:
11 if (
12 updated_task := await request.app.mongodb["tasks"].find_one({"_id": id})
13 ) is not None:
14 return updated_task
15
16 if (
17 existing_task := await request.app.mongodb["tasks"].find_one({"_id": id})
18 ) is not None:
19 return existing_task
20
21 raise HTTPException(status_code=404, detail=f"Task {id} not found")
Não queremos atualizar nenhum dos nossos campos para valores vazios, então, primeiro, removemos esses campos do documento de atualização. Como mencionado acima, como todos os valores são opcionais, uma solicitação de atualização com uma carga vazia ainda é válida. Depois de muita deliberação, decidi que, nessa situação, o certo a fazer pela API é retornar a tarefa não modificada e um status HTTP 200.
Se o usuário tiver fornecido um ou mais campos a serem atualizados, tentaremos $set os novos valores com update_one, antes de retornar o documento modificado. No entanto, se não conseguirmos encontrar um documento com o ID especificado, nosso roteador emitirá um 404.

Excluir

1@router.delete("/{id}", response_description="Delete Task")
2async def delete_task(id: str, request: Request):
3 delete_result = await request.app.mongodb["tasks"].delete_one({"_id": id})
4
5 if delete_result.deleted_count == 1:
6 return JSONResponse(status_code=status.HTTP_204_NO_CONTENT)
7
8 raise HTTPException(status_code=404, detail=f"Task {id} not found")
O roteador final não retorna um corpo de resposta em caso de sucesso, pois o documento solicitado não existe mais, já que acabamos de excluí-lo. Em vez disso, ele retorna um status HTTP 204, o que significa que a solicitação foi concluída com êxito, mas o servidor não tem dados para fornecer a você.

O front-end do React

O front-end do React não muda, pois está apenas consumindo a API e, portanto, é um pouco agnóstico em relação ao back-end. São principalmente os arquivos padrão gerados por create-react-app. Portanto, para iniciar nosso front-end do React, abra uma nova janela de terminal - mantendo o servidor FastAPI em execução no terminal existente - e digite os seguintes comandos no diretório do front-end.
1npm install
2npm start
Esses comandos podem demorar um pouco para serem concluídos, mas posteriormente, deve-se abrir uma nova janela do navegador para http://localhost:3000.
Captura de tela da linha do tempo no navegador
O front-end do React é apenas uma visualização da nossa lista de tarefas, mas você pode atualizar suas tarefas por meio da documentação da FastAPI e ver as alterações aparecerem no React!
Screencast do aplicativo TODO final
A maior parte do código do front-end está em frontend/src/App.js
1useEffect(() => {
2 const fetchAllTasks = async () => {
3 const response = await fetch("/task/")
4 const fetchedTasks = await response.json()
5 setTasks(fetchedTasks)
6 }
7
8 const interval = setInterval(fetchAllTasks, 1000)
9
10 return () => {
11 clearInterval(interval)
12 }
13}, [])
Quando nosso componente é montado, iniciamos um intervalo que é executado a cada segundo e obtemos a lista mais recente de tarefas antes de armazená-las em nosso estado. A função retornada no final do hook será executada sempre que o componente desmontar, limpando nosso intervalo.
1useEffect(() => {
2 const timelineItems = tasks.reverse().map((task) => {
3 return task.completed ? (
4 <Timeline.Item
5 dot={<CheckCircleOutlined />}
6 color="green"
7 style={{ textDecoration: "line-through", color: "green" }}
8 >
9 {task.name} <small>({task._id})</small>
10 </Timeline.Item>
11 ) : (
12 <Timeline.Item
13 dot={<MinusCircleOutlined />}
14 color="blue"
15 style={{ textDecoration: "initial" }}
16 >
17 {task.name} <small>({task._id})</small>
18 </Timeline.Item>
19 )
20 })
21
22 setTimeline(timelineItems)
23}, [tasks])
O segundo gancho é acionado sempre que a lista de tarefas em nosso estado muda. Este gancho cria um componente Timeline Item para cada tarefa em nossa lista.
1<>
2 <Row style={{ marginTop: 50 }}>
3 <Col span={14} offset={5}>
4 <Timeline mode="alternate">{timeline}</Timeline>
5 </Col>
6 </Row>
7</>
A última parte de App.js é a marcação para renderizar as tarefas na página. Se você já trabalhou com MERN ou outra pilha React antes, isso provavelmente lhe parecerá muito familiar.

Encerrando

Estou muito empolgado com a pilha da FARM, e espero que você também esteja. Podemos criar aplicativos web assíncronos e de alto desempenho usando minhas tecnologias favoritas! No meu próximo artigo, vamos ver como você pode adicionar autenticação aos seus aplicativos FARM.
Enquanto isso, confira a documentação da FastAPI e do Motor, bem como os outros pacotes e links úteis nesta lista Incrível da FastAPI.
Se tiver dúvidas, acesse o site da nossa comunidade de desenvolvedores, no qual os engenheiros e a comunidade do MongoDB ajudarão você a desenvolver sua próxima grande ideia com o MongoDB.

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Como usar os vetores quantizados do Cohere para criar aplicativos de AI econômicos com o MongoDB


Oct 03, 2024 | 23 min read
Artigo

Usando o SuperDuperDB para acelerar o desenvolvimento de IA no MongoDB Atlas Vector Search


Sep 18, 2024 | 6 min read
Tutorial

RAG interativo com MongoDB Atlas + API de chamada de função


Sep 18, 2024 | 16 min read
Início rápido

Introdução às transações ACID multidocumento em Python


Sep 11, 2024 | 10 min read
Sumário