## Create and Run in Venv or on server port
```bash
poetry init &&
poetry shell &&
poetry add 'fastapi[all]'
```
After Code is setup and ready to run:
```bash
# runs the app in the main.py file with the --reload option so that
# changes when the file is saved are hot loaded into the running API service
# on the port where the API is listening
# assumes there is a python package
# . My_Repo_Name
# ├─── app/
# │ ├─── __init__.py
# │ └─── main.py
# │ └─── app()
uvicorn app.main:app --reload
```
## Create a FastAPI
```python
from fastapi import FastAPI
app = FastAPI()
```
## Documentation
Auto generated documentation available on the API's port by default and is automatically constructed!
http://127.0.0.1:8000/docs
## Path definitions for API End Points
<https://calmcode.io/til/fastapi-two-decorators.html>
> Let's consider a very basic FastAPI app that has a health endpoint.
```python
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def get_health():
return {"status": "alive"}
```
> This `/health` endpoint has one job: to return a response with status 200. It can serve as a signal that the service is healthy and this is typically used by cloud providers as a indication that a service might need a restart.
>
> Some services however, don't use `/health` but `/healthz` instead. So how might you implement this? You could add another route.
```python
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def get_health():
return {"status": "alive"}
@app.get("/healthz")
def get_health():
return {"status": "alive"}
```
> But there's a simpler way, you can also just do this:
```python
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
@app.get("/healthz")
def get_health():
return {"status": "alive"}
```
> You can totally stack decorators in Python, and FastAPI will just consider it another route that needs to be added.
>
> So you only need a single line of code. Neat!
### ROOT
```python
# This is a path operation / or also called a route
@app.get('/') # this actually converts this function into an API end point that the user can access
# │ └──── The path operation, this is what happens when the users goes to the root, it path was `/login` then the user would only get here if they went to our site and `/login`
# └───────── The GET HTTP method is used to retrieve data from a server
async def root():
return {"message": "Hello from my API"} # fastapi automatically converts to JSON
```
Without Comments:
```python
@app.get('/')
async def root():
return {"message": "Hello from my API"}
```
### POST
```python
from fastapi.params import Body
from pydantic import BaseModel
@app.post('/posts') # V1
def create_posts(payLoad: dict = Body(...)): # Take in the body (data) of the POST request and do stuff with it
print(payLoad)
return {"new_post": f"{payLoad['title'] =} {payLoad['content'] =}"}
#===========================================================================#
@app.post('/posts') # V2
def create_posts(new_post: Post): # Take in the body (data) of the POST request and do stuff with it
print(new_post)
return {"new_post": f"{new_post.title =} {new_post.content =}"}
```
### GET
```python
@app.get('/posts') # in this instance this data gets returned to the user at 127.0.0.1:8000/posts
# Due to granularity for the paths in the decorators if there are duplicates then the first one that matches is chosen
def get_posts():
return {"data": "this is your posts"}
```
Get Without Comments:
```python
@app.get('/posts')
def get_posts():
return {"data": "this is your posts"}
```
### PUT
```python
@app.put("/posts/{id}")
def update_post(id: int, post: Post):
index = find_index_post(id)
if index == None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id: {id} does not exist")
post_dict = post.dict()
post_dict['id'] = id
my_posts[index] = post_dict
print(post)
return {"data": post_dict}
```
### DELETE
```python
@app.delete("/posts/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_post(id: int):
index = find_index_post(id)
if index == None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id: {id} does not exist")
my_posts.pop(index)
return Response(status_code=status.HTTP_204_NO_CONTENT)
```
## Order matters
If your API function has a path parameter then your other more specific paths need to come before it so that it doesnt think part of the path is the expected input parameter
```python
@app.get("/posts/latest")
def get_latest_post():
post = my_posts[len(my_posts) - 1]
return {"detail": post}
@app.get("/posts/{id}")
def get_post(id: int):
post = find_post(id)
return {"post_detail": post}
```
## Authentication
![[2022-01-11-10-25-47.png]]
## Test Client
```python
from fastapi import FastAPI
from fastapi.testclient import TestClient
from app.main import app
import pytest
client = TestClient(app)
def test_root():
result = client.get('/')
print(result.json().get('message'))
assert result.json().get('message') == 'Hello World'
assert result.status_code == 200
# return {"msg": "Hello World"}
def test_create_user():
result = client.post("/users/", json={"email": "
[email protected]","password": "password123"})
# print(result.json().get('message'))
print(result.json())
assert result.json().get('email') == "
[email protected]"
assert result.status_code == 201
```
## HTTP Status Codes
### 204
Cant delete something already deleted or doesnt exist and therefore cannot delete. No data returned
```python
@app.delete("/posts/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_post(id: int):
index = find_index_post(id)
if index == None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id: {id} does not exist")
my_posts.pop(index)
return Response(status_code=status.HTTP_204_NO_CONTENT)
```
### 404
```python
from fastapi import FastAPI, Response, status, HTTPException
app = FastAPI()
@app.get("/posts/{id}")
def get_post(id: int, response: Response):
post = find_post(id)
if not post:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail=f"post with id: {id} was not found")
return {"post_detail": post}
```
## Router
```markdown
.
└───app
│ database.py
│ utils.py
│ __main__.py
│
└───routers
post.py
user.py
```
using routers you can break apart your API code into separate modules
### In the submodules
```python
from fastapi import APIRouter
router = APIRouter()
# change the @app decorator from `@app` to `@router`
```
### In the main file
```python
from .routers import post, user
app.include_router(post.router)
```
this is like a [[header-file]] where all the code gets dumped into
that location. So this is a way of separating out the code to separate files
cleanly.
### Prefixes
Adding a prefix to the router object makes it so you can keep the endpoint path
short and sweet.
passing this:
```python
router = APIRouter (
prefix="/posts"
)
```
makes it so you can take this code:
```python
@app.get('/posts')
def get_posts():
...
@app.get("/posts/latest")
def get_latest_post():
...
@app.get("/posts/{id}")
def get_post():
...
```
And turn it into this:
```python
@app.get('')
def get_posts():
...
@app.get("/latest")
def get_latest_post():
...
@app.get("/{id}")
def get_post():
...
```
### Documenation Tags
Specifying tags in the router object makes the documentation for granularly
defined and easier to read.
```python
router = APIRouter(
prefix='/posts',
tags='posts'
)
```
results in something like this:
![[2022-01-11-10-22-42.png]]