Resources
In this course, we'll build a social media API. Users will be able to publish posts and write comments on them.
Let's start with publishing posts:
- Everyone will be able to post posts, without signing up first.
- Everyone will be able to see everyone's posts.
Doing this makes it much simpler. We don't have to worry about user authentication or the concept of "followers". We'll add those in later!
Creating an endpoint to receive a user's post
Imagine we are backend developers in a social media company, and we're tasked with making this API. There's another team that is tasked with making the website, and a third team that makes the mobile app. Both the website and mobile app will use your API, and that ensures that they both work in the same way.
APIs work by allowing clients (in our case, there's two) to send it data. The API then can do some processing, such as storing it in the database or sending an email, and then it responds to the client with some other data.
The first step in allowing users to publish posts is to add an API endpoint to which their client can send data. It doesn't matter to us if the client is the website or the mobile app, both will send us the same data. We'll then store that data in our database and respond with a success message.
First let's define what data clients will need to send us for their posts. Let's begin with something simple: the user's name and their post. Both of these are just text.
Where does the client get the user's name from?
As the API developer, we really don't care! The website or app could ask the user for their name when the user writes the post, or when they hit 'Publish', or at any other time. It's simply not something we need to worry about.
FastAPI relies strongly on another library called pydantic
to model the different bits of data a user might send. To create the model, create a file called models/post.py
with this content:
from pydantic import BaseModel
class UserPost(BaseModel):
name: str
body: str
Now let's import that model in our main.py
file:
from fastapi import FastAPI
from models.post import UserPost
app = FastAPI()
And then let's create an endpoint to which users can send JSON. We'll then give the JSON to our model, and it will spit back out a Python object with two attributes: name
and body
.
The endpoint will use an HTTP POST method. The HTTP method is something that every request to our API will have. It's just a piece of data in the request that helps tell the API what the request is for.
What are HTTP methods?
The HTTP method is just some data in the request. For example, if the method is GET
, that usually means the user wants our API to give them some data. If the method is POST
, that usually means the user wants to send our API some data (to tell our API to create something, or store something in the database).
There are also other HTTP methods, such as PUT
, PATCH
, DELETE
, and others.
Important: the HTTP methods themselves don't have much meaning. It's up to our API to decide what to do with each request, and the HTTP method gives us a bit more context about what the user wants.
Since the user wants us to create something in our API, let's use the POST
HTTP method for our endpoint:
from fastapi import FastAPI
from models.post import UserPost
app = FastAPI()
@app.post("/post")
async def create_post(post: UserPost):
return post
Something very cool about FastAPI is how it uses Python type hinting to simplify the definition of our API. Here by telling it that the post
parameter is of type UserPost
, it will automatically pass the incoming request's JSON body through our UserPost
model. Then a few things happen:
- The
UserPost
model validates the incoming JSON. It makes sure thename
andbody
fields are in there, and they can be converted to strings if they aren't already. - Converts the JSON into a
UserPost
object and passes that to ourcreate_post
function, so thatpost
is a Python object with two attributes:name
andbody
. We don't have to parse the JSON ourselves. - Helps populate the autogenerated documentation with information about what the client should send the API.
We've made the endpoint return the newly created post, but we could just return a message saying "Post created successfully" or something along those lines. In any case, the client should understand that receiving the newly created post JSON means "success".
Storing and returning user posts
Now let's store the post in a list so that future API requests can read them:
from fastapi import FastAPI
from models.post import UserPost
app = FastAPI()
posts = []
@app.post("/post")
async def create_post(post: UserPost):
posts.append(post)
return post
All we've done here is added the posts
global variable, and then after we receive the client's data, we add the post
to the list.
Finally, let's make another endpoint that returns all the posts for everyone to see:
from fastapi import FastAPI
from models.post import UserPost
app = FastAPI()
posts = []
@app.post("/post", response_model=UserPost)
async def create_post(post: UserPost):
posts.append(post)
return post
@app.get("/posts", response_model=list[UserPost])
async def get_all_posts():
return posts
Here we've addedresponse_model=UserPost
and response_model=list[UserPost]
to the decorators, beside the endpoint URL itself. This tells FastAPI how to convert our response to JSON.
The create_post
endpoint will pass the post
object to UserPost
, and be turned to a dictionary. FastAPI then returns the dictionary as JSON text.
The get_all_posts
endpoint will expect a list of UserPost
objects. Each object will be turned into a dictionary by the pydantic
library, and then the list will be returned as JSON text.
HTTP status codes
Just as the HTTP method is given to us by the client of our API and gives us a bit more information about what they want to achieve, we can return some data that tells the client the status of their request.
I'm sure you already know of some infamous status codes like 404
or 500
. Each status code means something specific, such as "Not Found" or "Internal Server Error".
The default status code, which means "OK"
, is 200
. That's what FastAPI will use for all responses unless we tell it to do something else.
When we create something we should also return a specific status code: 201
. Let's use that when we create posts:
from fastapi import FastAPI
from models.post import UserPost
app = FastAPI()
posts = []
@app.post("/post", status_code=201, response_model=UserPost)
async def create_post(post: UserPost):
posts.append(post)
return post