What is a GraphQL

GraphQL is an API-oriented query language. In the early days of the Internet, when requirements were mostly Web based, data and business requirements were not complex, RestAPI can be used to meet requirements. However, with the development of the Internet, the amount of data increases, and the business needs are changeable. There are also various clients that need interfaces, and the RestAPi-based approach is getting clunky, so GraphQL was born. It offers at least three advantages

  1. GraphQL provides more convenient API queries

Different clients sometimes need to return data in different formats. Previously, RestAPI was used to provide a separate interface for each client. As business requirements increase, the cost of maintenance jumps randomly and exponentially. Using GraphQL is much more fun. You just need to write a set of interfaces

  1. Resolve the dependency on the front and rear ends

During the development process, the front end and the back end need to go back and forth to validate each field, so that you don’t have to change the code in the middle of the development because you don’t have the fields right. What type of fields do you need? Write your own query syntax

  1. Save network and computer memory resources

A big problem with writing interfaces through RestAPI is that the definition of interfaces requires a lot of work in the early stage and various efforts to split interfaces, but even so, it is not able to cope with the sudden changes in requirements. Sometimes only a certain type of data of a user needs to be returned, but other information of the user has to be returned, which wastes network resources and consumes computer performance. Obviously not elegant enough, GraphQL proves once again that it can provide DIY access to the data you need, how much you use, how much you take, and it’s pretty green

PS: See the resources at the end of this article for more on GraphQL

introduce

In this article, I will set up a GraphQL + MongoDB project using a Todo List example. We will use the following libraries, which need to be installed before we start:

  1. graphene_mongo
  2. graphene
  3. mongoengine
  4. flask_graphql
  5. Flask

Before we start, let’s sort out our core requirements. We want to build a Todo List product. We only have two core tables, one is the user table, which stores all the user information, and the other is the task table, which stores all the user’s task information. The task table is associated with the corresponding user by user ID. The table structure corresponds to a one-to-many relationship, and the core data fields are as follows:

The task table

{ 
    "_id" : ObjectId("5c353fd8771502a411872712"), 
    "_in_time" : "The 2019-01-09 08:26:53"."_utime" : "The 2019-01-09 09:26:39"."task" : "read"."start_time" : "The 2019-01-09 08:26:53"."end_time" : "The 2019-01-09 08:26:53"."repeat" : [
        "Wed"]."delete_flag" : NumberInt(0), 
    "user" : "1"
}
Copy the code

The user table

{ 
    "_id" : "1"."_in_time" : "The 2019-01-09 08:39:16"."_utime" : "The 2019-01-09 09:23:25"."nickname" : "xiao hong"."sex" : "female"."photo": "http://xh.jpg"."delete_flag" : NumberInt(0)
}
Copy the code

The project structure

A picture is worth a thousand words. In order to have a clearer understanding of the overall structure of the project, I printed down the overall directory structure of the project. Friends can refer to the directory structure to see the next steps of building

----task_graphql\ |----api.py |----database\ | |----__init__.py | |----base.py | |----model_task.py | |----model_user.py  |----requirements.txt |----schema.py |----schema_task.py |----schema_user.pyCopy the code

  • User_model and task_model define data modules that connect directly to the database mongo
  • Upper-layer Schema operations shemA_user and schemA_task add, delete, modify, and query data models
  • Finally, Flask constructs the external API service implementation and interacts with external requests

Creating a data model

The structure of our data model is very simple

  • User_model lists all user information
  • Task_model Lists all task information. The user field is associated with the user table to indicate which user the task belongs to

base.py
from mongoengine import connect

connect("todo_list", host="127.0.0.1:27017")
Copy the code

You simply specify the corresponding database link information and database by calling MongoEngine’s Connect, which is then imported directly into the Flask module to automatically identify connections

model_user.py
import sys
sys.path.append("..")

from mongoengine import Document
from mongoengine import (StringField, IntField)
from datetime import datetime


class ModelUser(Document):

    meta = {"collection": "user"}

    id = StringField(primary_key=True)
    _in_time = StringField(required=True, default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    _utime = StringField(required=True, default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    nickname = StringField(required=True)
    sex = StringField(default="unknown", required=True)
    delete_flag = IntField(default=0, required=True)
Copy the code

All data documents to be defined are inherited through MongoEngine’s Document, which can convert the corresponding fields into class attributes for later operations on the data. Meta fields specify which mongo table you need to link to

model_task.py
import sys
sys.path.append("..")

from mongoengine import Document
from mongoengine import (StringField, ListField, IntField, ReferenceField)

from .model_user import ModelUser
from datetime import datetime


class ModelTask(Document):

    meta = {"collection": "task"}
    
    _in_time = StringField(default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), required=True)
    _utime = StringField(default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), required=True)
    task = StringField(default="", required=True)
    start_time = StringField(default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), required=True)
    end_time = StringField(default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), required=True)
    repeat = ListField(StringField(required=True))
    delete_flag = IntField(default=0, required=True)
    user = ReferenceField(ModelUser, required=True)
Copy the code

Required indicates that the field is required, and default can set the default value of the field. ReferenceField can specify which model to be associated with. Here, the specified is the ModelUser field, and the association defaults to the _ID field in the corresponding MONgo table

Create the GraphQL query

Now that we have the database and model section connected, let’s create the API section. In our task_graphQL directory, we have two files, Schema_task.py and schema_user.py map model_task and model_user classes to Graphene Schema objects, respectively

schema_task.py
from database.model_task import ModelTask
from graphene_mongo import MongoengineObjectType

import graphene
import schema_user

from datetime import datetime


class TaskAttribute:id = graphene.ID() _in_time = graphene.String() _utime = graphene.String() task = graphene.String() start_time = graphene.String() end_time = graphene.String() repeat = graphene.List(graphene.String) delete_flag = graphene.Int() user  = graphene.String()class Task(MongoengineObjectType):

    class Meta:
        model = ModelTask


class TaskNode(MongoengineObjectType):
    class Meta:
        model = ModelTask
        interfaces = (graphene.relay.Node, )
Copy the code
schema_user.py
from database.model_task import ModelTask
from graphene_mongo import MongoengineObjectType

import graphene

from datetime import datetime

class TaskAttribute:id = graphene.ID() _in_time = graphene.String() _utime = graphene.String() task = graphene.String() start_time = graphene.String() end_time = graphene.String() repeat = graphene.List(graphene.String) delete_flag = graphene.Int() user  = graphene.String()class Task(MongoengineObjectType):

    class Meta:
        model = ModelTask

class TaskNode(MongoengineObjectType):
    class Meta:
        model = ModelTask
        interfaces = (graphene.relay.Node, )
Copy the code

Now we create a schema.py file and import the schema_task.py and schema_user.py files we just defined to define two interfaces for external access

  • Tasks: Queries all tasks and returns a list
  • Users: Queries information about all users and returns a list
import schema_user
import schema_task
import graphene
from graphene_mongo.fields import MongoengineConnectionField


class Query(graphene.ObjectType):

    node = graphene.relay.Node.Field()

    tasks = MongoengineConnectionField(schema_task.TaskNode)

    users = MongoengineConnectionField(schema_user.UserNode)

schema = graphene.Schema(query=Query)
Copy the code

Creating Flask applications

Create an api.py file in the home directory, import the database connection and schema we defined earlier, and associate the two using Flask’s add_url_rule method. For easy access, We introduced the GraphQLView method of Flask_GraphQL to visualize the interface for easy debugging

from flask import Flask
from schema import schema
from flask_graphql import GraphQLView
from database.base import connect
from logger import AppLogger

log = AppLogger("task_graphql.log").get_logger()

app = Flask(__name__)
app.debug = True

app.add_url_rule("/graphql", view_func=GraphQLView.as_view("graphql", schema=schema, graphiql=True))

if __name__ == '__main__':
    app.run()
Copy the code

At this point, we have successfully created a queryable Todo List interface with GraphQL. We can use it to test the query interface. Then you need to mock your own data into Mongo before starting the query

We access interface address (http://127.0.0.1:5000/graphql), look to query results

Add the GraphQL update method (mutation)

GraphQL officially integrated all update creation operations under mutation, which included the functions of inserting and updating data. Next, we will continue the above operations to improve these functions

schema_task.py
from database.model_task import ModelTask
from graphene_mongo import MongoengineObjectType

import graphene

from datetime import datetime


class TaskAttribute:id = graphene.ID() _in_time = graphene.String() _utime = graphene.String() task = graphene.String() start_time = graphene.String() end_time = graphene.String() repeat = graphene.List(graphene.String) delete_flag = graphene.Int() user  = graphene.String()class Task(MongoengineObjectType):

    class Meta:
        model = ModelTask


class TaskNode(MongoengineObjectType):
    class Meta:
        model = ModelTask
        interfaces = (graphene.relay.Node, )


class CreateTaskInput(graphene.InputObjectType, TaskAttribute):
    pass


class CreateTask(graphene.Mutation):

    task = graphene.Field(lambda: TaskNode)

    class Arguments:
        input = CreateTaskInput(required=True)

    def mutate(self, info, input):
        task = ModelTask(**input)
        task.save()
        return CreateTask(task=task)


class UpdateTask(graphene.Mutation):

    task = graphene.Field(lambda: TaskNode)

    class Arguments:
        input = CreateTaskInput(required=True)

    def mutate(self, info, input):
        id = input.pop("id")
        task = ModelTask.objects.get(id=id)
        task._utime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        task.update(**input)
        task.save()
        return UpdateTask(task=task)
Copy the code
schema_user.py
from database.model_user import ModelUser
from graphene_mongo.types import MongoengineObjectType
import graphene
from datetime import datetime


class UserAttribute:
    id = graphene.String()
    _in_time = graphene.String()
    _utime = graphene.String()
    nickname = graphene.String()
    photo = graphene.String()
    sex = graphene.String()
    delete_flag = graphene.Int()


class User(MongoengineObjectType):

    class Meta:
        model = ModelUser


class UserNode(MongoengineObjectType):

    class Meta:
        model = ModelUser
        interfaces = (graphene.relay.Node, )


class CreateUserInput(graphene.InputObjectType, UserAttribute):
    pass


class CreateUser(graphene.Mutation):

    user = graphene.Field(lambda: UserNode)

    class Arguments:
        input = CreateUserInput(required=True)

    def mutate(self, info, input):
        user = ModelUser(**input)
        user.save()
        return CreateUser(user=user)


class UpdateUser(graphene.Mutation):

    user = graphene.Field(lambda: UserNode)

    class Arguments:
        input = CreateUserInput(required=True)

    def mutate(self, info, input):
        id = input.pop("id")
        user = ModelUser.objects.get(id=id)
        user._utime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        user.update(**input)
        user.save()
        return UpdateUser(user=user)
Copy the code

As you can see from the code, we pass in the information we need to add through input, and then map the parameters. Let’s take a look at creating data with an example

Let’s try modifying the data again, like this

Bingo!!!!!!

At this point, the GraphQL with MongoDB operation is closed perfectly.

The full project can be found at github: github.com/hacksman/ta…

The above are their own way after stepping over a lot of pits summed up the method, if there are omissions, but also hope to correct

The resources

  • Flask-Graphene-SQLAlchemy
  • What exactly is GraphQL that I often hear about?
  • Explaining GraphQL Connections
  • Preliminary study on GraphQL & Relay