Building a FastAPI + Strawberry (Graphql) Backend with Next.js 13 and React Server Components, Pt. 2

Building a FastAPI + Strawberry (Graphql) and NextJS 12 and React Server Components App

Alright, time to really start digging into this crazy thing.

The app I'm building is a project I've been trying to get up and going for years, even before I was a programmer - a Fantasy Football app.

I think this is a really good project for anyone to work on, the concepts and relationships are really simple, and there's a lot of data to mess around with that can be used to build out complex structures.

For years the hardest part of the project was getting actual NFL data, but thankfully I came across an NFL data library for Python that made getting basic data very straightforward and easy: https://github.com/cooperdff/nfl_data_py

Building A User Domain

Going off the previous post's structure, the Domain for the User will be structured as such:

/app
  /domains
    /users
      + resolvers
      + models
      + queries
      + mutations
      + types
      + utils

First things first is that we need to create the data table for the user, which will go in the models file:

from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy import Column, String

from uuid import uuid4

from ..core.database import Base


class User(Base):
    """
    table for users
    """
    __tablename__ = 'users'

    id = Column(UUID(as_uuid=True), primary_key=True, unique=True, default=uuid4)
    name = Column(String(80))
    email = Column(String(120), index=True)

    class Config:
        orm_mode = True

Dang, I love SQLAlchemy, that structure is super clean.

I think this is pretty self-explanatory, but definitely make sure to add this snipped at the end of the table:

class Config:
    orm_mode = True

According to FastAPI's documentation on ORMs, aka "object-relational mapping", this pattern converts database tables into objects to be used in code.

There are several to use, but FastAPI's docs recommend SQLAlchemy.

This Config setting allows you now to use User as an object to interact with Postgres's databases. This is beyond handy.

Now that we've defined our User model, we now need to use Alembic to migrate our database to create our table.

Following Jeff Astor's Migrating Our Database guide on using Alembic, we can create the User table within our data base with some very easy steps he outlines using the command line within your Docker container.

The biggest difference between Astor's guide is that we're going to use strawberry.ID to provide our default ID values to our primary id columns:

import strawberry

def upgrade() -> None:
    op.create_table(
        'users',
        sa.Column('id', UUID(as_uuid=True), primary_key=True, unique=True, default=strawberry.ID),
        sa.Column('name', sa.String(80)),
        sa.Column('email', sa.String(120), index=True)
    )
    pass

The final part of creating User is to create a Strawberry type associated with our object:

# domains/users/types.py

import strawberry


@strawberry.type
class User:
    id: strawberry.ID
    name: str
    email: str

Any frontend developer that's worked with Apollo and Typescript knows the importance of type declaration for a GraphQL API, and what we see above will become much more important when diving into the frontend of this project.

But for now, feel free to read through Strawberry's Documentation on Object Types.

Building A Query

Remember, the biggest strength of GraphQL is how it handles a request and response for data, and how it trims out properties that aren't requested specifically by the client.

For example, our User response has the possibility of returning id, name, and email but that's only if requested by the client.

For more complex requests that we'll get to later, this functionality becomes extremely powerful for helping keep data requests slim and trim.

Thankfully, though, Strawberry handles this for us, and all we really need to handle is structuring our Queries and Mutations properly.

Let's start with a very simple request, one without variables (aka an input filter in Strawberry), for all of the users in the database.

from typings import List
import strawberry

from domains.users.types import User


@strawberry.type
class UserQuery:
    @strawberry.field
    def users(self) -> List[User]:
        [...]

I'm betting that's a lot simpler than you thought.

Just to break it down, we're declaring a strawberry type of UserQuery (incidentally the same type as User), then defining a field on that type for users that will then return a list of the type of users.

There's an alternate, and simpler syntax for Queries and fields that you can find in Strawberry's Docs on Queries, but I'm preferring to use this syntax because of some functionality I'll get into later.

But if we were choosing to use the simpler syntax it would look like this:

@strawberry.type
class UserQuery:
    users: List[User] = strawberry.field(resolver=[...])

The next step is connecting the Query to it's resolver.

Since resolvers and controllers are the same thing the examples outlined in FastAPI's documentation can be applied here.

# domains/users/resolvers.py

from typings import List

from domains.users.types import User as UserType
from domains.users.models import User as UserModel

def get_all_users(db: Session) -> List[UserType]:
    return db.query(UserModel).all()

Then plug it into the query:

# domains/users/queries.py

from domains.users.resolvers import get_all_users

@strawberry.type
class UserQuery:
    @strawberry.field
    def users(self) -> List[User]:
        return get_all_users()

If anyone is following along at home, you'll find that this query doesn't work. And the reason being the db: Session param in get_all_users.

I'll address this in the next post, but I'm going to leave it for now, because following FastAPI's documentation on connecting endpoints and databases outlines the basics on what this means, but there's a couple of differences using Strawberry that I'll get dive into later.

With your Docker container running, navigate to the exposed Strawberry GraphQL endpoint, for me it's just http://localhost:8008/graphql but your might be different depending on your Docker settings. Strawberry ships with it's own GraphQL playground, and you can test your endpoint with the following query:

query AllUsers {
  users {
    id
    name
    email
  }
}

Building A Mutation

Thankfully in Strawberry mutations are basically the same structure as a query, but with a couple of minor differences.

Remember in GraphQL that Mutations cover the C, U, and D portion of CRUD, and do the heavy lifting for your API.

Let's create a mutation to register a new user:

# domains/user/mutations.py

import strawberry

from domains.user.types import User


@strawberry.input
class RegisterUserInput:
    name: str
    email: str
    password: str


@strawberry.type
class UserMutation:
    @strawberry.mutation
    def register_user(self, input: RegisterUserInput) -> User:
        [...]

Note that instead of using @strawberry.field we're going to use @strawberry.mutation in our UserMutation type.

I first implemented this using field and it seemed to work the same, so I'm not sure if this helps with anything more than internal referencing but it's still good to differentiate mutations from queries.

The other big difference is the RegisterUserInput input type.

The input type will help keep the params of the mutation (or query) cleaner and will provide type helping for the client for inputs.

Just like with our query, this mutation now needs to be connected to it's resolver:

# domains/users/resolvers.py

from domains.user.types import User as UserType
from domains.user.models import User as UserModel


def add_user_db(name: str, email: str, db: Session) -> UserType:
    """
    Add a user to db
    """
    user_db: UserModel = UserModel(name=name, email=email)

    db.add(user_db)
    db.commit()
    db.refresh(user_db)

    return user_db
# domains/users/mutations.py

from domains.user.resolvers import add_user_db
from domains.user.types import User


@strawberry.type
class UserMutation:
    @strawberry.mutation
    def register_user(self, input: RegisterUserInput) -> User:
      return add_user_db(name=input.name, email=input.email, password=input.password)

Again, note that this won't work until the db is resolved in the resolver, but for now this is good enough to see the relationship between the resolver and the mutation.

Returning to our GraphQL playground, the mutation can now be handled as such:

mutation AddUser (
  $name: String!
  $email: String!
  $password: String!
) {
  registerUser (input: {
    name: $name
    email: $email
    password: $password
  }) {
    name
    email
    id
  }
}

And in the section below the entry, you'll need to add the variables to be passed into AddUser for $name, $email, and $password.

I haven't included it here, but if you notice the password is obviously not stored directly in the User database and instead used to create an Auth item for this particular user, also outlined in FastAPI's documentation on security which is extremely thorough.

In the next post I'll dig into connecting up the db portion of the resolvers and how to do filtered queries and authentication on calls.