DEV Community

Cover image for GraphQL By Example: With Graphene, Flask, and Fauna
Curious Paul
Curious Paul

Posted on

GraphQL By Example: With Graphene, Flask, and Fauna

Let’s talk about GraphQL shall we?. In this article, I’ll be going over how to create simple GraphQL servers the conventional way, with web frameworks (Flask in particular), and then later in the article, we’ll observe how to make things easier by creating GraphQL servers the serverless way using Fauna’s seamless GraphQL integration features.

Who this article is for

This article is in general for intermediate web developers and more so for those who are looking for hands-on experience with building GraphQL servers. I can’t exactly put a cap on the kinds of developers who would be reading this, but for the sake of having a great experience reading this, it’s presumed that you already know a thing or two about GraphQL (not necessary to have built a GraphQL server), and can already build a REST server, you understand GraphQL query syntax to some extent if you’re unfamiliar here’s the docs to help you get started. Regardless of what kind of developer you are, I hope this helps you in any way it can.

Also If you’re already comfortable with building a GraphQL server from scratch in Flask, you can skip this first part and move on to serverless graphql APIs with Fauna.

Here’s a table of contents to help put things into perspective as regards what we’ll be doing in this article.

Table Of Contents

  • A brief introduction to GraphQL
  • Building a GraphQL server with Flask and Graphene
  • Serverless GraphQL server with Fauna

A Brief Introduction To GraphQL

GraphQL is a query language for APIs. In other words, it’s the syntax that you write in order to describe the kind of data that you want from APIs. What this means for you as a backend developer is that with GraphQL, you are able to expose a single endpoint on your server to handle GraphQL queries from client applications, as opposed to the many endpoints you’d need to create to handle specific kinds of requests with REST and in turn serve static data from those endpoints. If a client needs new data that isn’t already provisioned you’d need to create a new endpoint for it and update the API docs. GraphQL makes it possible to send queries to a single endpoint, these queries are then passed on to the server to be handled by the server’s predefined resolver functions and the requested information can be provided over the network.

To help illustrate, consider a simple note-app, one where users can sign-up to create as many notes as they like, nothing fancy. A typical REST API built for this would have multiple endpoints to handle writes to the database (POSTs and PUTs), as well as endpoints to retrieve data from the database (GETs).
The diagram below helps to illustrate what a typical REST workflow might be like between client applications and servers with their databases.

Alt Text

As observed clients have to interact with the server via multiple communication channels (endpoints) exposed by the server. Consumers of the API, have no control over what data gets returned when they request data via (GET) protocol. With GraphQL however, clients can in effect prescribe how the data they want should look like or at least what it must contain via these “queries”, GraphQL queries to be precise. The diagram below helps to put things into perspective.

Alt Text

Observe that the requests are still being sent over HTTP, hence POSTS, and GETs are still valid kinds of requests with GraphQL servers, the only difference is that you can customize your requests with GraphQL queries, as opposed to JSON or query parameters to get whatever kind of data you want or send data. Because it’s customizable on the grounds of it being a query syntax you get to decide what data you want and get only what you requested from the backend. GraphQL is a revolutionary tool in web-development If you’d like to find out more about GraphQL and get some fundamental knowledge about the tool you can click here to go to the docs.

Let’s now begin our journey by seeing how to build a simple note-app GraphQL server with Flask, and Graphene then see how we can make our lives easier with serverless by leveraging Fauna's GraphQL API.

GraphQL with Python (Flask and Graphene)

We’ll be building a simple note-app web server that’s capable of handling GraphQL queries with Flask (a Python web framework for building web servers) and Graphene (which is a python library that adds GraphQL support to python and allows you write GraphQL schemas in python). To be able to follow along, you must have Python installed, and have some previous experience in both Python and Flask.

Hands-on

  • We’ll begin by creating a virtual environment for our project and install the dependencies we need inside of the virtual environment, with virtualenv. Make a new folder called “noteapp” and cd into that folder from the terminal or cmd (on Windows) and create a new virtualenv there as follows:

virtualenv env

Next, we need to activate the virtual environment before we can install our dependencies in it. We do this by calling the “activate.bat” script in the virtualenv. I’m on Linux so this is done as follows:

source env/bin/activate

Once this is done you should see the name of the environment prefix the current directory path on the terminal (usually in brackets), as shown here

Alt Text

  • Once this is done we can use pip to install the required packages for this project. We’ll install: Flask, Sqlalchemy, Flask-graphql, Bcrypt, Flask-Jwt-Extended, Python-dotenv, Graphene, and graphene_sqlalchemy as follows:

pip install flask sqlalchemy flask-graphql graphene python-dotenv flask-jwt-extended bcrypt graphene_sqlalchemy

  • As of now, our folder should only contain a folder called “env/” since that’s what we called our virtualenv. Let’s create some of the files we’ll be needing for the app.

    • core/: add a new folder called core/ to “noteapp” as a sibling folder to env/. This folder will house our server app’s instance and its config
    • core/init.py: In this new folder add a dunder(double underscore) init file. This is where we’ll add our flask app instance, some config as well as our views.
    • extensions.py: outside of core/ add a new file called extensions.py it’ll hold instances of some extensions we’ll be using alongside our server.
    • models.py: let’s also add a file to hold our models for the database since we’re using an ORM (sqlalchemy)
    • schema.py: this is where we’ll define our graphql schema using graphene
    • data.py: this file simply creates our database and tables as defined by the models in models.py
    • run.py: this file is what we use to run our flask app.
    • .env: this is an env file that holds environment variables to be used by our app.

If this folder structure is quite confusing, kindly check out my other article on how to build modularized flask apps, which should help clear things out.
If you did it correctly your folder structure should look like this:

Alt Text

Let’s now take a look at each one and see what goes into them. Disclaimer!: as a quick reminder, this is not a beginner’s Flask course so I’m not going to explicitly explain some of the syntaxes you might see in the following sections, except it has to do with GraphQL which is what this article is all about in the first place.

  • models.py: As I mentioned this is where we’ll add our database models, we use Sqlalchemy extensively here. Below is what our models.py file looks like:
from sqlalchemy.orm import (
    scoped_session, relationship,
    sessionmaker
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import *
import os

basedir = os.getcwd()
engine = create_engine(f"sqlite:///{basedir}/dev.db")
session = scoped_session(
    sessionmaker(
        autocommit=False,
        autoflush=False,
        bind=engine
    )
)
Base = declarative_base(bind=engine)
Base.query = session.query_property()


# Note class
class Notes(Base):
    __tablename__ = 'notes'
    id = Column(Integer, primary_key=True)
    title = Column(String(100))
    body = Column(Text)
    user_id = Column(Integer, ForeignKey('user.id'))
    user = relationship('User', back_populates="notes")


class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    first_name = Column(String(100))
    last_name = Column(String(100))
    email = Column(String(100))
    password = Column(String(200))
    notes = relationship('Notes', back_populates="user")

Enter fullscreen mode Exit fullscreen mode

Pretty basic models, we have a User class to represent our user table and one for the Notes table. I’m using SQLite here, but you can use whatever database you want to make this work.

  • Schema.py: This is the fun part, here is where we use Graphene to create graphql schemas for our server. This schema defines how queries should be written to the server, and what kinds of data can be requested from the server. An example is to define a query for all notes in the database, and would be as follows:
type User {
    name: String!
    email: String!
    password: String!
    notes: [Notes!] @relation
}

type Notes {
    title: String!
    body: String!
    author: User! @relation
}

type Query {
    allNotes: [Notes!]!
}
Enter fullscreen mode Exit fullscreen mode

This is what a raw or native graphql schema looks like and this defines primitive types, namely: Notes (which represents Note objects from the database), User (which represents User objects from the database) as well as a query called allNotes to get all notes from the Notes table.

If you don’t understand this syntax please refer to the Graphql docs. We won’t however be writing native schema, there’s a rather easier way to do this with graphene by generating these “types” (Notes and User as depicted above), directly from our models, this is made possible by graphene-sqlalchemy which basically serializes our sqlalchemy models into a format Graphene can work with in order to create schemas like the one in the image above.

Let’s see how we can achieve this:

from models import (
    User as UserModel, Notes as NotesModel,
    session
)
import graphene
from graphene_sqlalchemy import (
    SQLAlchemyConnectionField,
    SQLAlchemyObjectType
)
from extensions import bcrypt
from typing import Optional

# types
class User(SQLAlchemyObjectType):
    class Meta:
        model = UserModel


class Notes(SQLAlchemyObjectType):
    class Meta:
        model = NotesModel
Enter fullscreen mode Exit fullscreen mode

Here we import the required classes from graphene-sqlalchemy as well as our models, we then create a class (which subclasses SQLAlchemyObjectType) to represent each of the models. These new classes will be used throughout our schema to create query and mutation schemas. A query is used to retrieve information and is in essence sent as a GET request, mutations are used to write to the database, so they carry information and are sent as POSTs or PUTs.

Let’s see how we might define a mutation to help with registration. Usually, a mutation in native form looks something like this:

Alt Text

Here we have a mutation “createUser” that takes in name, email, and password as string arguments, and returns a user type which can then be further queried. Let's see how we can implement this in graphene. So after declaring our types with graphene-sqlalchemy in the previous step add the following lines to define the createUser mutation.

# registeration
class createUser(graphene.Mutation):
    class Arguments:
        first_name = graphene.String()
        last_name = graphene.String()
        email = graphene.String()
        password = graphene.String()
    ok = graphene.Boolean()
    user = graphene.Field(User)

    def mutate(root, info, first_name, last_name, email, password):
        new_user = UserModel(
            first_name=first_name,
            last_name=last_name,
            email=email,
            password=str(
                bcrypt.generate_password_hash(password),
                'utf-8'
            )
        )
        session.add(new_user)
        session.commit()
        ok = True
        return createUser(ok=ok, user=new_user)

Enter fullscreen mode Exit fullscreen mode

As observed we simply subclass graphene.Mutation which is used to define mutations, and create another class called “Arguments” inside of createUser to hold arguments for our mutation. The mutation also declares its return types, ok(a boolean type) and user(which is of type User, as we declared earlier on with our models).

This is followed by a method called “mutate” that handles the request by creating a user object(with the models directly this time not the serialized user type) and uses sqlalchemy session to commit it to the database.

Lastly, we return an instance of createUser along with the status of the request with the ok variable and the newly created user object. The mutate method takes in the root and info as positional arguments. These are passed down from the Mutate class, we need them to process our queries correctly; they basically hold useful information about the query as well as its context.

Once a request is sent the mutation creates the new user and returns a response. We’ll add a couple more mutations and queries. As follows:

  • Add a new note: This schema represents a mutation query to add a new note
# add new note
class addNote(graphene.Mutation):
    class Arguments:
        title = graphene.String()
        body = graphene.String()
    ok = graphene.Boolean()
    note = graphene.Field(Notes)

    def mutate(root, info, title, body):
        uid = info.context['uid']
        # find user based on token payload
        user = session.query(UserModel).filter_by(email=uid).first()
        new_note = NotesModel(
            title=title,
            body=body,
            user=user
        )
        session.add(new_note)
        session.commit()
        ok = True
        return addNote(ok=ok, note=new_note)
Enter fullscreen mode Exit fullscreen mode
  • Update an existing note:
# update existing note
class updateNote(graphene.Mutation):
    class Arguments:
        note_id = graphene.Int()
        title = graphene.String()
        body = graphene.String()
    ok = graphene.Boolean()
    note = graphene.Field(Notes)

    def mutate(root, info, note_id, title: Optional[str]=None, body: Optional[str]=None):
        # find note object
        note = session.query(NotesModel).filter_by(id=note_id).first()
        if not title:
            note.body = body
        elif not body:
            note.title = title
        else:
            note.title = title
            note.body = body
        session.commit()
        ok = True
        note = note
        return updateNote(ok=ok, note=note)
Enter fullscreen mode Exit fullscreen mode
  • Delete a Note :
# delete note
class deleteNote(graphene.Mutation):
    class Arguments:
        id = graphene.Int()
    ok = graphene.Boolean()
    note = graphene.Field(Notes)

    def mutate(root, info, id):
        note = session.query(NotesModel).filter_by(id=id).first()
        session.delete(note)
        ok = True
        note = note
        session.commit()
        return deleteNote(ok=ok, note=note)
Enter fullscreen mode Exit fullscreen mode

Before moving on to GET queries, one final thing we must do after defining mutation classes is to mount the instances of each of our mutation classes as query fields so that they can be used to query our server - using the ObjectType class from graphene. It’s a generic type that’s used to define queries, mutations just have to be defined using the Mutation class first as seen above before declaring them as fields as we will now do below:


class PostAuthMutation(graphene.ObjectType):
    addNote = addNote.Field()
    updateNote = updateNote.Field()
    deleteNote = deleteNote.Field()


class PreAuthMutation(graphene.ObjectType):
    create_user = createUser.Field()

Enter fullscreen mode Exit fullscreen mode

I’m making two classes because I’ll be creating a pair of schemas for logged in/authorized users and non-authorized users. Here, I add the mutations that are accessible by authorized users in PostAuthMutation and define them as fields using the Field() method on each one of them.

  • Queries: Queries to request for data from the server are simply called “queries”; these can be defined using the “ObjectType” class. Multiple queries can be defined in a single class with multiple resolver functions to handle each one if need be.
class Query(graphene.ObjectType):
    # find single note
    findNote = graphene.Field(Notes, id=graphene.Int())
    # get all notes by user
    user_notes = graphene.List(Notes)

    def resolve_user_notes(root, info):
        # find user with uid from token
        uid = info.context['uid']
        user = session.query(UserModel).filter_by(email=uid).first()
        return user.notes

    def resolve_findNote(root, info, id):
        return session.query(NotesModel).filter_by(id=id).first()

Enter fullscreen mode Exit fullscreen mode

ObjectTypes are allowed to describe their fields, and this particular query has two fields described. FindNote by Id, and user_notes which represents an array of all notes by a particular user.
Observe that each field is described by a native graphene type; Field for a single value field and List to represent a field with an array of values. Each one takes the data-type for each field as its first argument. The findNote query for instance is defined as one that expects a Note-type and a keyword parameter which is what is used by the resolver to find the data.

After describing our fields we add resolver methods to handle the request. There is a naming convention for our methods and it's usually of the format - “resolver_”. This is how graphene knows which function belongs to what field. I’ll define one more query for non-authorized users.

class PreAuthQuery(graphene.ObjectType):
    all_users = graphene.List(User)

    def resolve_all_users(root, info):
        return session.query(UserModel).all()
Enter fullscreen mode Exit fullscreen mode

Lastly, we’ll define two schemas, one for authorized requests and the other for non-authorized requests.

auth_required_schema = graphene.Schema(query=Query, mutation=PostAuthMutation)
schema = graphene.Schema(query=PreAuthQuery, mutation=PreAuthMutation)
Enter fullscreen mode Exit fullscreen mode
  • extensions.py: This contains instances of some important extensions/libraries used in this project. They are used throughout the application.
from flask_bcrypt import Bcrypt
from flask_graphql_auth import GraphQLAuth
from flask_jwt_extended import JWTManager

bcrypt = Bcrypt()
auth = GraphQLAuth()
jwt = JWTManager()
Enter fullscreen mode Exit fullscreen mode
  • data.py: I use this file to run the command that creates the database and the tables defined in the models.py file.
from models import (
    session, Base
)

# create tables
Base.metadata.create_all()

Enter fullscreen mode Exit fullscreen mode
  • run.py: here’s where we add the command to run our flask server, to run the app we’ll simply run this file:
from core import app
import json

if __name__ == "__main__":
    app.run(debug=True)
Enter fullscreen mode Exit fullscreen mode
  • core/init.py: Finally let’s talk about the actual server code, there’s quite a number of important things going on here. But the majority of what is being done here is regular flask setup. This is also where we’ll use flask-graphql and add the schemas we declared from schema.py to our graphql endpoints. To begin we’ll import the required dependencies, instantiate our flask app, add its configuration and pass in the flask app instance to the extensions that need it
from flask import Flask, request, jsonify
from flask_graphql import GraphQLView
from extensions import bcrypt, auth, jwt
from schema import auth_required_schema, schema
from dotenv import load_dotenv
from flask_jwt_extended import (
    create_access_token,
    create_refresh_token, get_jwt_identity,
    jwt_required
)
import os
from models import User, session

load_dotenv()  # loads environment variable from .env file

app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('secret')
app.config['JWT_SECRET_KEY'] = os.getenv('jwtsecret')

bcrypt.init_app(app)
auth.init_app(app)
jwt.init_app(app)
Enter fullscreen mode Exit fullscreen mode

Next, we define a couple of endpoints, two graphql endpoints, one for
Authorized requests and the other for non-authorized requests, as well as conventional REST-style login endpoint to create auth tokens.

@app.route('/')
def index():
    return "Go to /graphql"


@app.route('/login', methods=['POST'])
def login():
    # fetch login credentials
    data = request.get_json(force=True)
    # find user
    user = session.query(User).filter_by(email=data['email']).first()
    if not user:
        return {
            "ok":True,
            "message": "User with email not found"
        }, 404
    if bcrypt.check_password_hash(user.password, data['password']):
        token = create_access_token(identity=data['email'])
        return jsonify(access_token=token)
    return {
        "ok":True,
        "message": "Incorrect password"
    }, 401
Enter fullscreen mode Exit fullscreen mode

Here we have two endpoints, one to tell the user what route the graphql server can be accessed on and the other for login. This login route is a REST endpoint and takes in JSON to log in a user and returns a token as JSON as well, this token is then used to send requests to the protected graphql endpoint - by adding it to headers of the request. Let’s see how we can use Flask-graphql to define GraphQL endpoints.

def graphql():
    view = GraphQLView.as_view(
        'graphql',
        schema=auth_required_schema,
        graphiql=True,
        get_context=lambda: {
            'session': session,
            'request':request,
            'uid': get_jwt_identity()
        }
    )
    return jwt_required(view)


app.add_url_rule(
    '/graphql',
    view_func=graphql()
)

app.add_url_rule(
    '/graphq',
    view_func=GraphQLView.as_view(
        'graphq',
        schema=schema,
        graphiql=True
    )
)
Enter fullscreen mode Exit fullscreen mode

I use the flask app’s add_url_rule method to create new routes; tantamount to using the @app.route decorator, Flask also provides this method, this way you can have your handlers as standalone functions perhaps even in separate files.
The first thing we have here is the handler for the /graphql route, and in there we use the GraphQLView.as_view() method to add our schema from schema.py and specify graphiql=True, this allows the server to render an interface to write graphql queries in and test them with ease. Also, note the get_context attribute, it's important because we use it to pass important values, like the user’s identity from the token and some other important things like db session - onto each request. If you observe in our mutations we used this ‘uid’ to get the user object that's currently logged in, get_context passes the data we give it onto the info parameter on our queries.

Hopefully, that’s clear enough, now let’s run our app! First, we need to run data.py so that it creates our database and tables as defined by models.py. To do this we use the data.py file, simply run it from the cmd or terminal using :

python data.py

  • Next, we’ll run the app using run.py, you should now see your server logs as usual. To test visit localhost:5000/graphq which is accessible without authorization, here is where we’ll create a new user account. On this page, you’ll find two panes. The left one is where you write your graphql queries and here, we’ll create a user as follows:

Alt Text

As expected we declared createUser to take in those arguments, and return the status along with the newly created user object, which can also be sub-queried.

  • Let’s login with localhost:5000/login, via postman or curl or any other means preferred, I have a postman browser extension and that's what I’m going to be using.

Alt Text

  • Next, we can go to localhost:5000/graphql with this token in the headers. If you try to open this route without the token you’ll get this :

Alt Text

As such I added the token from the login request to headers from my browser via a browser plugin called ModHeader; you don’t have to test this from the browser, you can do it from Postman or any other API testing tool you prefer. From here we can then write mutations to add a note, update notes or even delete notes, or queries to fetch all notes by the user currently signed in as an example :

Alt Text

Of Course, we get an empty array since we haven’t added anything, we can add a new note as follows:

Alt Text

  • So that's how to set-up a simple graphql server in python using Flask, and Graphene, and that wasn’t so bad now was it?. But what if there was an easier way to get our server up and running. In the next phase of this article, I’ll be showing you how to set up a GraphQL server without actually writing backend code, or bothering about setting up a server from scratch. The next part of this article shows you how to setup a GraphQL server using Fauna’s GraphQL API.

Serverless GraphQL APIs With Fauna

So first off some introduction, before we jump right in. It’s only fair that I give Fauna a proper introduction seeing as it's about to make our lives a whole lot easier, as we will soon get to see.

Serverless and Fauna

Alt Text

Fauna is a serverless database service, that handles all optimization and maintenance tasks so that developers don’t have to bother about them and can focus on developing their apps and shipping to market faster.

Then again what is serverless; so serverless doesn’t actually mean “NO SERVERS”, to simply put it - what serverless means instead is that you can get things working without necessarily having to set things up from scratch, for example, some apps that use serverless concept don’t have a backend service written from scratch, they instead employ the use of cloud functions which are, technically scripts written on cloud platforms to handle necessary tasks like login, registration, serving data, etc - all without having to set up a server from scratch.

Where does Fauna fit into all of this, you ask?. Well usually, when we build servers we need to provision our server with a database, and when we do that it's usually a running instance of our database server on the same machine that our server is running on or at least on the same network. With serverless technology like Fauna, we can shift that workload to the cloud and focus on actually writing our auth systems, implementing business logic for our app, etc. Fauna also manages things like, maintenance and scaling which are usually causes for concern with systems that use conventional databases.

That’s it for the intro, if you’re interested in getting more info about Fauna and its features check the docs here. Let’s get started with building our GraphQL API the serverless way with GraphQL.

Hands On With Fauna

Requirements

  • Fauna Account: That’s right, that’s all you need folks; an account with Fauna is all you need for this session, so click here to go to the sign-up page.

Creating a Fauna Database

  • Login to your Fauna Account once you have created an account. Once on the dashboard, you should see a button to create a new database, click on that and you should see a little form to fill in the name of the database, that resembles the one below:

Alt Text

I call mine “graphqlbyexample” but you can choose to call yours whatever you please, also ignore the pre-populate with demo data option we don’t need that for this demonstration. Click Save and you should be brought to a new screen as shown below:

Alt Text

Adding a GraphQL Schema To Fauna

  • In order to get our GraphQL server up and running, Fauna allows us to upload our own graphql schema, on the page we’re currently on you should see a GraphQL option; select that and it’ll prompt you to upload a schema file. This file usually contains raw graphQL schema and is saved with either the .gql or .graphql file extension. Let’s create our schema and upload it to Fauna to spin up our server.

Alt Text

  • Let’s create that file, create a new file anywhere you like, I’m creating it in the same directory as our previous app, because it has no impact on it, I’m also calling it schema.gql in schema.gql we’ll add the following:
type User {
    name: String!
    email: String!
    password: String!
    notes: [Notes!] @relation
}

type Notes {
    title: String!
    body: String!
    author: User! @relation
}
Enter fullscreen mode Exit fullscreen mode

Here we simply define our data types in tandem to our two tables (Notes, and User). Save this and go back to that page to upload this schema.gql that we just created. Once that’s done Fauna will process that and take us to a new page - our GraphQL API playground!!

Alt Text

We have literally created a graphql server by simply uploading that really simple schema to Fauna, and to highlight some of the really cool feats that Fauna has, observe:

  • Fauna automatically generates collections for us, If you notice we didn’t create any collection (translates to Tables, if you’re only familiar with relational databases - Fauna is a NoSQL database and collections are technically the same as tables, and documents as to rows in tables; check out the docs for more info). If we go to the collections options and click on that we’d see the tables that were auto-generated on our behalf, courtesy of the schema file that we uploaded.

Alt Text

  • Fauna automatically creates indexes on our behalf: head over to the Indexes option and see what indexes have been created on behalf of the API. Fauna is a document-oriented database and doesn’t have primary keys or foreign-keys as you’d have in relational databases for search and index purposes, instead, we create indexes in Fauna to help with data retrieval, find out more about indexes here.

Alt Text

  • Fauna automatically generates graphql queries and mutations as well as API Docs on our behalf: this one’s my personal favorite and I can’t seem to get over just how efficient Fauna does this. Fauna is able to intelligently generate some queries that it thinks you might want in your newly created API. Head over back to the GraphQL option and click on the “Docs” tab to open up the Docs on the playground.

Alt Text

As you can see we have two queries and a handful of mutations already auto-generated (even though we didn’t add them to our schema file), you can click on each one in the docs to see the details. Fauna’s thoughtful enough to help us create some boilerplate queries and mutations without us asking or having to hard code it in. Thank you Fauna.

Now without stalling any further, let’s run some tests!

Testing Our Server

Let's test out some of these queries and mutations from the playground, we can also use our server outside of the playground, by the way, it is a fully functional GraphQL server, after all, so we will look at how to do that as well

Testing From The Playground

  • We’ll test our first off by creating a new user, with the predefined createUser mutation as follows:

Alt Text

If we go to the collections options and choose User, we should have our newly created entry (document aka row) in our User collection.

  • Let’s also create a new note and associate it with a user as the author via its document ref id, which is a special ID generated by Fauna for all documents for the sake of references like this much like a key in relational tables. To find the ID for the user we just created simply navigate to the collection and from the list of documents you should see the option(a copy Icon) to copy Ref ID:

Alt Text

Once you have this you can create a new note and associate it as follows:

Alt Text

  • Let’s make a query this time, this time to get data from the database. Currently, we can fetch users by ID or fetch a note by its ID. Let's see that in action :

Alt Text

Yes you must have been thinking it, what if we wanted to fetch info of all users, currently, we can’t do that because Fauna didn’t generate that automatically for us, but we can update our schema so let’s add our custom query to our schema.gql file, as follows, note that this is an update to the file so don’t clear everything in the file out just add this to it:

Alt Text

Once you’ve added this save the file and click on the update schema option on the playground to upload the file again, it should take a few seconds to update, once it's done we’ll be able to use our newly created query, as follows:

Alt Text

Please don’t forget that as opposed to having all the info about users served (namely: name, email, password) we can choose what fields we want, cause yeah - it’s GraphQL !!, and not just that it's Fauna’s GraphQL so feel free to specify more fields if you want this is just for demo purposes so i only specified the name field to get all names of all users, and as you can observe the result is an array as clearly defined by the schema we just added.

Testing From Without the Playground - Using Python (requests library)

Now that we’ve seen that our API works from the playground lets see how we can actually use this from an application outside the playground environment, using python’s request library so if you don’t have it installed kindly install it using pip as follows:

pip install requests

  • Before we write any code we need to get our API key from Fauna which is what will help us communicate with our API from outside the playground. Head over to security on your dashboard and on the keys tab select the option to create a new key, it should bring up a form like this:

Alt Text

Leave the database option as the current one, change the role of the key from admin to the server and then save. It’ll generate for you a new key secret that you must copy and save somewhere safe, as an environment variable most probably.

  • For this I’m going to create a simple script to demonstrate, so add a new file call it whatever you wish - I’m calling mine test.py to your current working directory or anywhere you wish. In this file we’ll add the following:
import requests
import os

url = "https://graphql.fauna.com/graphql"

# create a request to fetch user by ID

# create query
query = """
    {
        findUserByID(id: 287471031836213761)
        {
            email
        }
    }
"""
token = os.getenv('FAUNA_SECRET')
# set headers 
headers = {"Authorization": f"Bearer {token}"}
Enter fullscreen mode Exit fullscreen mode

Here we add a couple of imports, including the requests library which we use to send the requests, as well as the os module used here to load our environment variables which is where I stored the Fauna secret key we got from the previous step.

Note the URL where the request is to be sent, this is gotten from the Fauna GraphQL playground here:

Alt Text

Next, we create a query which is to be sent this example shows a simple fetch query to find a user by id (which is one of the automatically generated queries from Fauna), we then retrieve the key from the environment variable and store it in a variable called a token, and create a dictionary to represent out headers, this is, after all, an HTTP request so we can set headers here, and in fact, we have to because Fauna will look for our secret key from the headers of our request.

  • The concluding part of the code features how we use the request library to create the request, and is shown as follows:
get = requests.post(
    url=url,
    json={'query': query},
    headers=headers
)

if get.status_code == 200:
    print(get.json())
else:
    print("Error sending request..")
Enter fullscreen mode Exit fullscreen mode

We create a request object and check to see if the request went through via its status_code and print the response from the server if it went well otherwise we print an error message lets run test.py and see what it returns:

Alt Text

Eureka! Now that it works, we can confirm that our API works off the playground as well so long as we communicate correctly with our secret key.

Conclusion

In this article, we’ve learned a lot about GraphQL - by example! now haven’t we?. We covered creating GraphQL servers from scratch and looked at creating servers right from Fauna without having to do much work, we also saw some of the awesome, cool perks that come with using the serverless system that Fauna provides, we went on to further see how we could test our servers and validate that they work.

Hopefully, this was worth your time, hope it taught you a thing or two about GraphQL, Serverless, Fauna, and Flask!. I would love to write more articles like this one, so be sure to leave positive remarks to encourage me, or even leave suggestions, if you feel like I left something out or perhaps if there was something you’d want to see me work on in a future article, please do leave a comment.

You can reach out to me on Twitter @Curiouspaul2 for further inquiries. Thank you!

Top comments (6)

Collapse
 
zenalc profile image
Mihir Shrestha • Edited

Nice guide, but it's not up-to-date unfortunately. I'm seeing a wrapper error when following along the guide.

To resolve, you need to instead just return the review and decorate the function with jwt_required().

Collapse
 
curiouspaul1 profile image
Curious Paul

Hi Mihir Could you give more details on the error you're getting, and are you able to tell what is producing this error

Collapse
 
charliearray profile image
Charles Ybarra

The current flask_jwt_extended (version 4.0.0) has breaking changes the way the current tutorial is written.
flask-jwt-extended.readthedocs.io/...

Thread Thread
 
curiouspaul1 profile image
Curious Paul

ah i see, that explains it. Do you mind sharing the specific changes?

Collapse
 
bkoiki950 profile image
Babatunde Koiki

Thanks for writing this.

Collapse
 
curiouspaul1 profile image
Curious Paul

Thanks boss I appreciate your comment