What is GraphQL? An Inside look at GraphQL - ZeptoBook

How to set up GraphQL with Golang: a deep dive from basics to advanced

How to set up GraphQL with Golang: a deep dive from basics to advanced

GraphQL has become a buzzword over the last few years after Facebook made it open-source. I have tried GraphQL with the Node.js, and I agree with all the buzz about the advantages and simplicity of GraphQL.

GraphQL has become a buzzword over the last few years after Facebook made it open-source. I have tried GraphQL with the Node.js, and I agree with all the buzz about the advantages and simplicity of GraphQL.

So what is GraphQL? This is what the official GraphQL definition says:

GraphQL is a query language for APIs and runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
I recently switched to Golang for a new project I’m working on (from Node.js) and I decided to try GraphQL with it. There are not many library options with Golang but I have tried it with Thunder, graphql, graphql-go, and gqlgen. And I have to say that gqlgen is winning among all the libraries I have tried.

gqlgen is still in beta with latest version 0.7.2 at the time of writing this article, and it’s rapidly evolving. You can find their road-map here. And now 99designs is officially sponsoring them, so we will see even better development speed for this awesome open source project. vektah and neelance are major contributors, and neelance also wrote graphql-go.

So let’s dive into the library semantics assuming you have basic GraphQL knowledge.

Highlights

As their headline states,

GraphQL is a query language for APIs and runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
I think this is the most promising thing about the library: you will never see map[string]interface{} here, as it uses a strictly typed approach.

Apart from that, it uses a Schema first Approach: so you define your API using the graphql Schema Definition Language. This has its own powerful code generation tools which will auto-generate all of your GraphQL code and you will just need to implement the core logic of that interface method.

I have divided this article into two phases:

  • The basics: Configuration, Mutations, Queries, and Subscription
  • The advanced: Authentication, Dataloaders, and Query Complexity

Phase 1: The Basics - Configuration, Mutations, Queries, and Subscriptions

We will use a video publishing site as an example in which a user can publish a video, add screenshots, add a review, and get videos and related videos.

mkdir -p $GOPATH/src/github.com/ridhamtarpara/go-graphql-demo/

Create the following schema in the project root:

type User {
    id: ID!
    name: String!
    email: String!
}

type Video {
    id: ID!
    name: String!
    description: String!
    user: User!
    url: String!
    createdAt: Timestamp!
    screenshots: [Screenshot]
    related(limit: Int = 25, offset: Int = 0): [Video!]!
}

type Screenshot {
    id: ID!
    videoId: ID!
    url: String!
}

input NewVideo {
    name: String!
    description: String!
    userId: ID!
    url: String!
}

type Mutation {
    createVideo(input: NewVideo!): Video!
}

type Query {
    Videos(limit: Int = 25, offset: Int = 0): [Video!]!
}

scalar Timestamp

Here we have defined our basic models and one mutation to publish new videos, and one query to get all videos. You can read more about the graphql schema here. We have also defined one custom type (scalar), as by default graphql has only 5 scalar types that include Int, Float, String, Boolean and ID.

So if you want to use custom type, then you can define a custom scalar in schema.graphql (like we have defined Timestamp) and provide its definition in code. In gqlgen, you need to provide marshal and unmarshal methods for all custom scalars and map them to gqlgen.yml.

Another major change in gqlgen in the last version is that they have removed the dependency on compiled binaries. So add the following file to your project under scripts/gqlgen.go.

// +build ignore

package main

import "github.com/99designs/gqlgen/cmd"

func main() {
	cmd.Execute()
}

and initialize dep with:

dep init

Now it’s time to take advantage of the library’s codegen feature which generates all the boring (but interesting for a few) skeleton code.

go run scripts/gqlgen.go init

which will create the following files:

gqlgen.yml — Config file to control code generation.

**generated.go **— The generated code which you might not want to see.

models_gen.go — All the models for input and type of your provided schema.

resolver.go — You need to write your implementations.

server/server.go — entry point with an http.Handler to start the GraphQL server.

Let’s have a look at one of the generated models of the Video type:

type Video struct {
	ID          string        `json:"id"`
	Name        string        `json:"name"`
	User        User          `json:"user"`
	URL         string        `json:"url"`
	CreatedAt   string        `json:"createdAt"`
	Screenshots []*Screenshot `json:"screenshots"`
	Related     []Video       `json:"related"`
}

Here, as you can see, ID is defined as a string and CreatedAt is also a string. Other related models are mapped accordingly, but in the real world you don’t want this — if you are using any SQL data type you want your ID field as int or int64, depending on your database.

For example I am using PostgreSQL for demo so of course I want ID as an int and CreatedAt as a time.Time. So we need to define our own model and instruct gqlgen to use our model instead of generating a new one.

type Video struct {
	ID          int       `json:"id"`
	Name        string    `json:"name"`
	Description string    `json:"description"`
	User        User      `json:"user"`
	URL         string    `json:"url"`
	CreatedAt   time.Time `json:"createdAt"`
	Related     []Video
}

// Lets redefine the base ID type to use an id as int
func MarshalID(id int) graphql.Marshaler {
	return graphql.WriterFunc(func(w io.Writer) {
		io.WriteString(w, strconv.Quote(fmt.Sprintf("%d", id)))
	})
}

// And the same for the unmarshaler
func UnmarshalID(v interface{}) (int, error) {
	id, ok := v.(string)
	if !ok {
		return 0, fmt.Errorf("ids must be strings")
	}
	i, e := strconv.Atoi(id)
	return int(i), e
}

func MarshalTimestamp(t time.Time) graphql.Marshaler {
	timestamp := t.Unix() * 1000

	return graphql.WriterFunc(func(w io.Writer) {
		io.WriteString(w, strconv.FormatInt(timestamp, 10))
	})
}

func UnmarshalTimestamp(v interface{}) (time.Time, error) {
	if tmpStr, ok := v.(int); ok {
		return time.Unix(int64(tmpStr), 0), nil
	}
	return time.Time{}, errors.TimeStampError
}

and update gqlgen to use these models like this:

schema:
- schema.graphql
exec:
  filename: generated.go
model:
  filename: models_gen.go
resolver:
  filename: resolver.go
  type: Resolver
models:
  Video:
    model: github.com/ridhamtarpara/go-graphql-demo/api.Video
  ID:
    model: github.com/ridhamtarpara/go-graphql-demo/api.ID
  Timestamp:
model: github.com/ridhamtarpara/go-graphql-demo/api.Timestamp

So, the focal point is the custom definitions for ID and Timestamp with the marshal and unmarshal methods and their mapping in a gqlgen.yml file. Now when the user provides a string as ID, UnmarshalID will convert a string into an int. While sending the response, MarshalID will convert int to string. The same goes for Timestamp or any other custom scalar you define.

Now it’s time to implement real logic. Open resolver.go and provide the definition to mutation and queries. The stubs are already auto-generated with a not implemented panic statement so let’s override that.

func (r *mutationResolver) CreateVideo(ctx context.Context, input NewVideo) (api.Video, error) {
	newVideo := api.Video{
		URL:         input.URL,
		Name:        input.Name,
		CreatedAt:   time.Now().UTC(),
	}

	rows, err := dal.LogAndQuery(r.db, "INSERT INTO videos (name, url, user_id, created_at) VALUES($1, $2, $3, $4) RETURNING id",
		input.Name, input.URL, input.UserID, newVideo.CreatedAt)
	defer rows.Close()

	if err != nil || !rows.Next() {
		return api.Video{}, err
	}
	if err := rows.Scan(&newVideo.ID); err != nil {
		errors.DebugPrintf(err)
		if errors.IsForeignKeyError(err) {
			return api.Video{}, errors.UserNotExist
		}
		return api.Video{}, errors.InternalServerError
	}
  
	return newVideo, nil
}

func (r *queryResolver) Videos(ctx context.Context, limit *int, offset *int) ([]api.Video, error) {
	var video api.Video
	var videos []api.Video

	rows, err := dal.LogAndQuery(r.db, "SELECT id, name, url, created_at, user_id FROM videos ORDER BY created_at desc limit $1 offset $2", limit, offset)
	defer rows.Close();
	
    if err != nil {
		errors.DebugPrintf(err)
		return nil, errors.InternalServerError
	}
	for rows.Next() {
		if err := rows.Scan(&video.ID, &video.Name, &video.URL, &video.CreatedAt, &video.UserID); err != nil {
			errors.DebugPrintf(err)
			return nil, errors.InternalServerError
		}
		videos = append(videos, video)
	}

	return videos, nil
}

and hit the mutation:

Ohh it worked…… but wait, why is my user empty 😦? So here there is a similar concept like lazy and eager loading. As graphQL is extensible, you need to define which fields you want to populate eagerly and which ones lazily.

I have created this golden rule for my organization team working with gqlgen:

GraphQL is a query language for APIs and runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
For our use-case, I want to load Related Videos (and even users) only if a client asks for those fields. But as we have included those fields in the models, gqlgen will assume that you will provide those values while resolving video — so currently we are getting an empty struct.

Sometimes you need a certain type of data every time, so you don’t want to load it with another query. Rather you can use something like SQL joins to improve performance. For one use-case (not included in the article), I needed video metadata every time with the video which is stored in a different place. So if I loaded it when requested, I would need another query. But as I knew my requirements (that I need it everywhere on the client side), I preferred it to load eagerly to improve the performance.

So let’s rewrite the model and regenerate the gqlgen code. For the sake of simplicity, we will only define methods for the user.

type Video struct {
	ID          int       `json:"id"`
	Name        string    `json:"name"`
	Description string    `json:"description"`
	UserID      int       `json:"-"`
	URL         string    `json:"url"`
	CreatedAt   time.Time `json:"createdAt"`
}

So we have added UserID and removed User struct and regenerated the code:

go run scripts/gqlgen.go -v

This will generate the following interface methods to resolve the undefined structs and you need to define those in your resolver:

type VideoResolver interface {
	User(ctx context.Context, obj *api.Video) (api.User, error)
	Screenshots(ctx context.Context, obj *api.Video) ([]*api.Screenshot, error)
	Related(ctx context.Context, obj *api.Video, limit *int, offset *int) ([]api.Video, error)
}

And here is our definition:

func (r *videoResolver) User(ctx context.Context, obj *api.Video) (api.User, error) {
	rows, _ := dal.LogAndQuery(r.db,"SELECT id, name, email FROM users where id = $1", obj.UserID)
	defer rows.Close()
	
	if !rows.Next() {
		return api.User{}, nil
	}
	var user api.User
	if err := rows.Scan(&user.ID, &user.Name, &user.Email); err != nil {
		errors.DebugPrintf(err)
		return api.User{}, errors.InternalServerError
	}
	
	return user, nil
}

Now the result should look something like this:

So this covers the very basics of graphql and should get you started. Try a few things with graphql and the power of Golang! But before that, let’s have a look at subscription which should be included in the scope of this article.

Subscriptions

Graphql provides subscription as an operation type which allows you to subscribe to real tile data in GraphQL. gqlgen provides web socket-based real-time subscription events.

You need to define your subscription in the schema.graphql file. Here we are subscribing to the video publishing event.

type Subscription {
    videoPublished: Video!
}

Regenerate the code by running: go run scripts/gqlgen.go -v.

As explained earlier, it will make one interface in generated.go which you need to implement in your resolver. In our case, it looks like this:

var videoPublishedChannel map[string]chan api.Video

func init() {
	videoPublishedChannel = map[string]chan api.Video{}
}

type subscriptionResolver struct{ *Resolver }

func (r *subscriptionResolver) VideoPublished(ctx context.Context) (<-chan api.Video, error) {
	id := randx.String(8)

	videoEvent := make(chan api.Video, 1)
	go func() {
		<-ctx.Done()
	}()
	videoPublishedChannel[id] = videoEvent
	return videoEvent, nil
}

func (r *mutationResolver) CreateVideo(ctx context.Context, input NewVideo) (api.Video, error) {
	// your logic ...

	for _, observer := range videoPublishedChannel {
		observer <- newVideo
	}

	return newVideo, nil
}

Now, you need to emit events when a new video is created. As you can see on line 23 we have done that.

And it’s time to test the subscription:

GraphQL comes with certain advantages, but everything that glitters is not gold. You need to take care of a few things like authorizations, query complexity, caching, N+1 query problem, rate limiting, and a few more issues — otherwise it will put you in performance jeopardy.

Phase 2: The advanced - Authentication, Dataloaders, and Query Complexity

Every time I read a tutorial like this, I feel like I know everything I need to know and can get my all problems solved.

But when I start working on things on my own, I usually end up getting an internal server error or never-ending requests or dead ends and I have to dig deep into that to carve my way out. Hopefully we can help prevent that here.

Let’s take a look at a few advanced concepts starting with basic authentication.

Authentication

In a REST API, you have a sort of authentication system and some out of the box authorizations on particular endpoints. But in GraphQL, only one endpoint is exposed so you can achieve this with schema directives.

You need to edit your schema.graphql as follows:

type Mutation {
    createVideo(input: NewVideo!): Video! @isAuthenticated
}

directive @isAuthenticated on FIELD_DEFINITION

We have created an isAuthenticated directive and now we have applied that directive to createVideo subscription. After you regenerate code you need to give a definition of the directive. Currently, directives are implemented as struct methods instead of the interface so we have to give a definition.

I have updated the generated code of server.go and created a method to return graphql config for server.go as follows:

func NewRootResolvers(db *sql.DB) Config {
	c := Config{
		Resolvers: &Resolver{
			db: db,
		},
	}

	// Schema Directive
	c.Directives.IsAuthenticated = func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
		ctxUserID := ctx.Value(UserIDCtxKey)
		if ctxUserID != nil {
			return next(ctx)
		} else {
			return nil, errors.UnauthorisedError
		}
	}
	return c
}
rootHandler:= dataloaders.DataloaderMiddleware(
		db,
		handler.GraphQL(
			go_graphql_demo.NewExecutableSchema(go_graphql_demo.NewRootResolvers(db)
		)
	)
http.Handle("/query", auth.AuthMiddleware(rootHandler))

We have read the userId from the context. Looks strange right? How was userId inserted in the context and why in context? Ok, so gqlgen only provides you the request contexts at the implementation level, so you can not read any of the HTTP request data like headers or cookies in graphql resolvers or directives. Therefore, you need to add your middleware and fetch those data and put the data in your context.

So we need to define auth middleware to fetch auth data from the request and validate.

I haven’t defined any logic there, but instead I passed the userId as authorization for demo purposes. Then chain this middleware in server.go along with the new config loading method.

Now, the directive definition makes sense. Don’t handle unauthorized users in your middleware as it will be handled by your directive.

Demo time:

You can even pass arguments in the schema directives like this:

directive @hasRole(role: Role!) on FIELD_DEFINITION
enum Role { ADMIN USER }

Dataloaders

This all looks fancy, doesn’t it? You are loading data when needed. Clients have control of the data, there is no under-fetching and no over-fetching. But everything comes with a cost.

So what’s the cost here? Let’s take a look at the logs while fetching all the videos. We have 8 video entries and there are 5 users.

query{
  Videos(limit: 10){
    name
    user{
      name
    }
  }
}

Query: Videos : SELECT id, name, description, url, created_at, user_id FROM videos ORDER BY created_at desc limit $1 offset $2
Resolver: User : SELECT id, name, email FROM users where id = $1
Resolver: User : SELECT id, name, email FROM users where id = $1
Resolver: User : SELECT id, name, email FROM users where id = $1
Resolver: User : SELECT id, name, email FROM users where id = $1
Resolver: User : SELECT id, name, email FROM users where id = $1
Resolver: User : SELECT id, name, email FROM users where id = $1
Resolver: User : SELECT id, name, email FROM users where id = $1
Resolver: User : SELECT id, name, email FROM users where id = $1

Why 9 queries (1 videos table and 8 users table)? It looks horrible. I was just about to have a heart attack when I thought about replacing our current REST API servers with this…but dataloaders came as a complete cure for it!

This is known as the N+1 problem, There will be one query to get all the data and for each data (N) there will be another database query.

This is a very serious issue in terms of performance and resources: although these queries are parallel, they will use your resources up.

We will use the dataloaden library from the author of gqlgen. It is a Go- generated library. We will generate the dataloader for the user first.

go get github.com/vektah/dataloaden
dataloaden github.com/ridhamtarpara/go-graphql-demo/api.User

This will generate a file userloader_gen.go which has methods like Fetch, LoadAll, and Prime.

Now, we need to define the Fetch method to get the result in bulk.

func DataloaderMiddleware(db *sql.DB, next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		userloader := UserLoader{
			wait : 1 * time.Millisecond,
			maxBatch: 100,
			fetch: func(ids []int) ([]*api.User, []error) {
				var sqlQuery string
				if len(ids) == 1 {
					sqlQuery = "SELECT id, name, email from users WHERE id = ?"
				} else {
					sqlQuery = "SELECT id, name, email from users WHERE id IN (?)"
				}
				sqlQuery, arguments, err := sqlx.In(sqlQuery, ids)
				if err != nil {
					log.Println(err)
				}
				sqlQuery = sqlx.Rebind(sqlx.DOLLAR, sqlQuery)
				rows, err := dal.LogAndQuery(db, sqlQuery, arguments...)
				defer rows.Close();
				if err != nil {
					log.Println(err)
				}
				userById := map[int]*api.User{}

				for rows.Next() {
					user:= api.User{}
					if err := rows.Scan(&user.ID, &user.Name, &user.Email); err != nil {
						errors.DebugPrintf(err)
						return nil, []error{errors.InternalServerError}
					}
					userById[user.ID] = &user
				}

				users := make([]*api.User, len(ids))
				for i, id := range ids {
					users[i] = userById[id]
					i++
				}

				return users, nil
			},
		}
		ctx := context.WithValue(r.Context(), CtxKey, &userloader)
		r = r.WithContext(ctx)
		next.ServeHTTP(w, r)
	})
}

Here, we are waiting for 1ms for a user to load queries and we have kept a maximum batch of 100 queries. So now, instead of firing a query for each user, dataloader will wait for either 1 millisecond for 100 users before hitting the database. We need to change our user resolver logic to use dataloader instead of the previous query logic.

func (r *videoResolver) User(ctx context.Context, obj *api.Video) (api.User, error) {
	user, err := ctx.Value(dataloaders.CtxKey).(*dataloaders.UserLoader).Load(obj.UserID)
	return *user, err
}

After this, my logs look like this for similar data:

Query: Videos : SELECT id, name, description, url, created_at, user_id FROM videos ORDER BY created_at desc limit $1 offset $2
Dataloader: User : SELECT id, name, email from users WHERE id IN ($1, $2, $3, $4, $5)

Now only two queries are fired, so everyone is happy. The interesting thing is that only five user keys are given to query even though 8 videos are there. So dataloader removed duplicate entries.

Query Complexity

In GraphQL you are giving a powerful way for the client to fetch whatever they need, but this exposes you to the risk of denial of service attacks.

Let’s understand this through an example which we’ve been referring to for this whole article.

Now we have a related field in video type which returns related videos. And each related video is of the graphql video type so they all have related videos too…and this goes on.

Consider the following query to understand the severity of the situation:

{
  Videos(limit: 10, offset: 0){
    name
    url
    related(limit: 10, offset: 0){
      name
      url
      related(limit: 10, offset: 0){
        name
        url
        related(limit: 100, offset: 0){
          name
          url
        }
      }
    }
  }
}

If I add one more subobject or increase the limit to 100, then it will be millions of videos loading in one call. Perhaps (or rather definitely) this will make your database and service unresponsive.

gqlgen provides a way to define the maximum query complexity allowed in one call. You just need to add one line (Line 5 in the following snippet) in your graphql handler and define the maximum complexity (300 in our case).

rootHandler:= dataloaders.DataloaderMiddleware(
	db,
	handler.GraphQL(
		go_graphql_demo.NewExecutableSchema(go_graphql_demo.NewRootResolvers(db)),
		handler.ComplexityLimit(300)
  ),
)

gqlgen assigns fix complexity weight for each field so it will consider struct, array, and string all as equals. So for this query, complexity will be 12. But we know that nested fields weigh too much, so we need to tell gqlgen to calculate accordingly (in simple terms, use multiplication instead of just sum).

func NewRootResolvers(db *sql.DB) Config {
	c := Config{
		Resolvers: &Resolver{
			db: db,
		},
	}

	// Complexity
	countComplexity := func(childComplexity int, limit *int, offset *int) int {
		return *limit * childComplexity
	}
	c.Complexity.Query.Videos = countComplexity
	c.Complexity.Video.Related = countComplexity

	// Schema Directive
	c.Directives.IsAuthenticated = func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
		ctxUserID := ctx.Value(UserIDCtxKey)
		if ctxUserID != nil {
			return next(ctx)
		} else {
			return nil, errors.UnauthorisedError
		}
	}
	return c
}

Just like directives, complexity is also defined as struct, so we have changed our config method accordingly.

I haven’t defined the related method logic and just returned the empty array. So related is empty in the output, but this should give you a clear idea about how to use the query complexity.

Final Notes

This code is on Github. You can play around with it, and if you have any questions or concerns let me know in the comment section.

Learn More

Build a Basic App with Spring Boot and JPA using PostgreSQL

How to Install PostgreSQL on Ubuntu 18.04

An Introduction to Queries in PostgreSQL

GraphQL Tutorial: Understanding Spring Data JPA/SpringBoot

How To Manage an SQL Database

GraphQL with React: The Complete Developers Guide

GraphQL with Angular & Apollo - The Full-stack Guide

GraphQL: Learning GraphQL with Node.Js

Complete guide to building a GraphQL API

GraphQL: Introduction to GraphQL for beginners

File Upload with Vue, Apollo Client and GraphQL

File Upload with Vue, Apollo Client and GraphQL

In this tutorial, I'll be showing you how to hand file uploads in GraphQL by building a fullstack app.

The tutorial will be divided into two main sections: building the GraphQL API and the frontend app. The GraphQL API will be built using Apollo Server and the frontend app will be built with Vue.js and Vue Apollo.

Table of Contents

  • Prerequisites
  • What we'll be building
  • Getting your Cloudinary keys
  • Building the GraphQL server
  • Building the frontend app
  • Fetching all photos
  • Uploading photo
  • Conclusion
Prerequisites

This tutorial assumes you conformatable with GraphQL and using it in a Vue app.

What we'll be building

For the purpose of this tutorial, we'll be building a simple photo album app, where users will be able to upload as well as view their photos. All the photos will be uploaded directly to Cloudinary. Below is a quick demo of the final app:

File Upload with Vue, Apollo Client and GraphQL

Getting your Cloudinary keys

Before we dive into code, let’s make sure we have our Cloudinary key in place. If you don’t already have an account with them, you can signup for free. Otherwise, login to your dashboard where you would see your account details along with your keys.

File Upload with Vue, Apollo Client and GraphQL

Building the GraphQL server

Now, let's start building the GraphQL server. First, let's create our project directory:

$ mkdir graphql-vue-photo-upload && cd graphql-vue-photo-upload
$ mkdir server && cd server
$ npm init -y

All the GraphQL API related code will be inside the server directory. Next, let’s install the necessary dependencies for our GraphQL server:

$ npm install graphql apollo-server cloudinary dotenv

Addition to installing Apollo Server and it dependence, we also install the Cloudinary Node.js library and a package to read environment variables.

Once that’s done installing, we can start building the GraphQL server. Create a new src directory and create a new index.js file inside it, then add the following code in it:

// server/src/index.js

const { ApolloServer } = require('apollo-server')
require('dotenv').config()
const typeDefs = require('./schema')
const resolvers = require('./resolvers')

const server = new ApolloServer({
  typeDefs,
  resolvers
})

server.listen().then(({ url }) => console.log(`Server ready at ${url}`))

Next, we need to create the schema and resolvers which our GraphQL server references. We’ll start with creating the schema. Create a schema.js inside src directory and paste the following code into it:

// server/src/schema.js

const { gql } = require('apollo-server')

const typeDefs = gql`type Photo {
    filename: String!
    path: String!
  }

  type Query {
    allPhotos: [Photo]
  }

  type Mutation {
    uploadPhoto(photo: Upload!): Photo!
  }`

module.exports = typeDefs

Here, we define a Photo type that comprises of two fields: the filename of the photo and the path to the actual photo. Then we define a single query allPhotos for fetching all uploaded photos. Lastly, we have a mutation for uploading a photo. The uploadPhoto mutation accepts a single argument, which is the photo that is to be uploaded. The argument is of the scalar type Upload, which is made available to us my Apollo Server, since it has built-in support of file upload. The mutation will return the uploaded photo.

The next, thing to do is create the resolvers. Still inside src directory, create a resolvers.js and add the code below in it:

// server/src/resolvers.js

const cloudinary = require('cloudinary').v2

cloudinary.config({
  cloud_name: process.env.CLOUD_NAME,
  api_key: process.env.API_KEY,
  api_secret: process.env.API_SECRET
})

const photos = []

const resolvers = {
  Query: {
    allPhotos () {
      return photos
    }
  },
  Mutation: {
    async uploadPhoto (parent, { photo }) {
      const { filename, createReadStream } = await photo

      try {
        const result = await new Promise((resolve, reject) => {
          createReadStream().pipe(
            cloudinary.uploader.upload_stream((error, result) => {
              if (error) {
                reject(error)
              }

              resolve(result)
            })
          )
        })

        const newPhoto = { filename, path: result.secure_url }

        photos.push(newPhoto)

        return newPhoto
      } catch (err) {
        console.log(err)
      }
    }
  }
}

module.exports = resolvers

First, we pull in the Cloudinary library and configured it will our credentials, which are getting from the environment variables. Then we create an empty array, which will hold our photos. Next, we define the resolver for the allPhotos query, which simply returns the photos array.

For the uploadPhoto mutation, Apollo Server would return the selected file as Promise, which contains a bunch of details about the file, such as: createReadStream, filename, mimetype and encoding. In this tutorial, we’ll only be making use of the first two, so we extract them from the object. Using createReadStream, we stream the file directly to Cloudinary. Since it is an asynchronous operation we wrap it in a Promise and awaits it. If the Promise was resolved, that is, the file was uploaded successfully to Cloudinary, we create a new object containing the uploaded file name and the Cloudinary path to the file. Then we push the new object to the photos array and finally return the new object.

Lastly, if there was an error uploading the file to Cloudinary, we simply console log the erorr.

Before we wrap up the GraphQL API, let’s quickly add our environment variables. Create a .env file directly in the server directory and add the code below in it:

// server/.env

CLOUD_NAME=YOUR_CLOUD_NAME
API_KEY=YOUR_API_KEY
API_SECRET=YOUR_API_SECRET

Remember to replace the placeholders with your actual account details.

Finally, let’s start the server:

$ node src/index.js

The server should be running on [[http://localhost:4000](http://localhost:4000)](http://localhost:4000](http://localhost:4000))

Building the frontend app

As already mentioned, the frontend app will be built with Vue.js, so let’s create a new Vue.js app using the Vue CLI:

$ vue create client

When prompted, press enter to select the default preset. Start the app and leave it running while we build on it:

$ cd client
$ yarn serve

The app should be running on http://localhost:8080

Once the Vue app is created, let’s install the necessary dependencies:

$ npm install vue-apollo graphql-tag graphql apollo-cache-inmemory apollo-client apollo-upload-client

Out of these dependencies, the one that is new and that I’d like to point out is [apollo-upload-client]([https://github.com/jaydenseric/apollo-upload-client)](https://github.com/jaydenseric/apollo-upload-client)). It’s a package for Apollo Client that allows us to send GraphQL multipart requests. It will be used in place of apollo-link.

Next, let’s configure Vue Apollo and these dependencies. Update main.js as below:

// client/src/main.js

import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloClient } from 'apollo-client'
import { createUploadLink } from 'apollo-upload-client'
import Vue from 'vue'
import VueApollo from 'vue-apollo'
import App from './App.vue'

Vue.config.productionTip = false

Vue.use(VueApollo)

const apolloClient = new ApolloClient({
  link: createUploadLink({ uri: 'http://localhost:4000' }),
  cache: new InMemoryCache()
})

const apolloProvider = new VueApollo({
  defaultClient: apolloClient
})

new Vue({
  apolloProvider,
  render: h => h(App)
}).$mount('#app')

You’ll notice here we are using createUploadLink from apollo-upload-client to create the ApolloClient link, passing to it our GraphQL API endpoint.

To give our app a bit of styling, we’ll be pulling in UIKit. Add the line below to head section of index.html:





Fetching all photos

We’ll start with the GraphQL query for fetching all our photo. Create a graphql directory inside the client/src directory and with in, create a AllPhotos.js file and paste the code below into it:

// client/src/graphql/AllPhotos.js

import gql from 'graphql-tag'

export default gql`query allPhotos {
    allPhotos {
      filename
      path
    }
  }`

For the sake of simplicity, we’ll be making use of just the App.vue component. So let’s update it as below:

// client/src/App.vue


  
    
      ## Photo Album


      
        
          
            
              ![](photo.path)
            
            {{ photo.filename }}

          
        
      
    
  



Within the apollo object, we add the ALL_PHOTOS query to fetch all photos and save it in a allPhotos. Once allPhotos is populated with data from our GraphQL API, we display the photos by looping through them.

If we view our app, we should get something similar to below:
File Upload with Vue, Apollo Client and GraphQL

Uploading photo

Of course, we need to have uploaded some photos before we can see them. Let’s implement that now. Still inside the graphql directory, create a UploadPhoto.js and paste the code below into it:

// client/src/graphql/UploadPhoto.js

import gql from 'graphql-tag'

export default gql`mutation uploadPhoto($photo: Upload!) {
    uploadPhoto(photo: $photo) {
      filename
      path
    }
  }`

Next, add the snippet below to the template section of App.vue, just below the Photo Album heading:

// client/src/App.vue


  

Here, we have a file input field that accepts only images. On change of the input field a uploadPhoto method is triggered.

In the script section, add:

// client/src/App.vue

import UPLOAD_PHOTO from "./graphql/UploadPhoto";

methods: {
  async uploadPhoto({ target }) {
    await this.$apollo.mutate({
      mutation: UPLOAD_PHOTO,
      variables: {
        photo: target.files[0]
      },
      update: (store, { data: { uploadPhoto } }) => {
        const data = store.readQuery({ query: ALL_PHOTOS });

        data.allPhotos.push(uploadPhoto);

        store.writeQuery({ query: ALL_PHOTOS, data });
      }
    });
  }
}

We get extract the target from the input event, then call the mutate method, passing to it the UPLOAD_PHOTO mutation as well as the required argument (through the variables object). We get the selected file from the files on the target object. Once the mutation is executed, we update the cache by adding the newly uploaded photo to the allPhotos array.
File Upload with Vue, Apollo Client and GraphQL

File Upload with Vue, Apollo Client and GraphQL

Conclusion

So in this tutorial, we have seen how to handle file uploads in GraphQL using Apollo Server on the server side and Vue and Vue Apollo on the client side. Though we used Cloudinary to store our photos, you can easily wrap it for any other cloud storage service or you can even save directly to your own local filesystem.

How to build a simple CRUD with GraphQL and Flutter

How to build a simple CRUD with GraphQL and Flutter

In this tutorial, I am going to show you how to build a simple CRUD with Flutter (client-side) and GraphQL (server-side)

Originally published by Daniel Berrío Barrera at https://medium.com

Flutter is an SDK (Software development kit) to build applications for Android and IOs with a single codebase. On the other hand, GraphQL is a query language that allows us to create queries that will be executing on the server. In this tutorial, I am going to show you how to build a simple CRUD with Flutter (client-side) and GraphQL (server-side). Let’s start

Once we have created our Flutter project we need to import some dependencies in our pubspec.yaml file in order to use GraphQL and HTTP with our API. It’s essential to notice that the API was created on NodeJS, so for this tutorial I’m not going to show how to build it.

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^2.0.1
  http: ^0.12.0+2

Then, we are going to create a file called graphQLConf.dart, this file will contain the basic configuration to use GraphQL like our link to the API and the cache configuration. So, we create a class called GraphQLConfiguration, inside this, there are three main parts. The first one is the link to the API, the second one is the ValueNotifier which will notify its listener when any kind of value changed, and finally, the method clientToQuery() will let us make queries and mutation in our application.

import "package:flutter/material.dart";
import "package:graphql_flutter/graphql_flutter.dart";

class GraphQLConfiguration {
static HttpLink httpLink = HttpLink(
uri: "https://examplegraphql.herokuapp.com/graphql",
);

ValueNotifier<GraphQLClient> client = ValueNotifier(
GraphQLClient(
link: httpLink,
cache: OptimisticCache(dataIdFromObject: typenameDataIdFromObject),
),
);

GraphQLClient clientToQuery() {
return GraphQLClient(
cache: OptimisticCache(dataIdFromObject: typenameDataIdFromObject),
link: httpLink,
);
}
}

In our main.dart file we will wrap the MaterialApp Widget with the previous configuration as follow:

import 'package:flutter/material.dart';
import "package:graphql_flutter/graphql_flutter.dart";
import "services/graphqlConf.dart";
import "package:example/components/principal.dart";

GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration();

void main() => runApp(
GraphQLProvider(
client: graphQLConfiguration.client,
child: CacheProvider(child: MyApp()),
),
);

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Example',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Principal(),
);
}
}

As you can saw we called the GraphQLObject from the graphQLConf.dart file and then we wrap it into the main method. But we don’t know how the Principal Widget use there is made. Basically, during this example we are going to work with a class Person, this class will contain basic information such as the id, the name, the last name, and the age. The application lets us add a person, edit a person, see all the persons added and finally delete a person (a basic CRUD). So let’s create a class person.

class Person {
Person(this.id, this.name, this.lastName, this.age);

final String id;
final String name;
final String lastName;
final int age;

getId() => this.id;

getName() => this.name;

getLastName() => this.lastName;

getAge() => this.age;
}

As you can see is a simple class Person with the id, name, lastName and age attributes, also we had a constructor and the get methods to access its values. Then we need to do our queries to the API. GraphQL provides two forms:

  • Query: It allows us to access data and read it.
  • Mutation: It allows to modify our data. Add, edit or delete something.

For this, let’s create a class that will contain all the queries and mutation that we are going to use. (Note: Dart uses a notation with three quotes to make the queries).

class QueryMutation {
String addPerson(String id, String name, String lastName, int age) {
return """
mutation{
addPerson(id: "$id", name: "$name", lastName: "$lastName", age: $age){
id
name
lastName
age
}
}
""";
}

String getAll(){
return """
{
persons{
id
name
lastName
age
}
}
""";
}

String deletePerson(id){
return """
mutation{
deletePerson(id: "$id"){
id
}
}
""";
}

String editPerson(String id, String name, String lastName, int age){
return """
mutation{
editPerson(id: "$id", name: "$name", lastName: "$lastName", age: $age){
name
lastName
}
}
""";
}
}

Then, our Principal widget will use an AlertDialog to do the insert, read and delete operations, and then it will be called from our Widget. So let’s create the AlertDialog widget.

import "package:flutter/material.dart";
import "../services/graphqlConf.dart";
import "../services/queryMutation.dart";
import "package:graphql_flutter/graphql_flutter.dart";
import "package:example/components/person.dart";

class AlertDialogWindow extends StatefulWidget {
final Person person;
final bool isAdd;

AlertDialogWindow({Key key, this.person, this.isAdd}) : super(key: key);

@override
State<StatefulWidget> createState() =>
_AlertDialogWindow(this.person, this.isAdd);
}

class _AlertDialogWindow extends State<AlertDialogWindow> {
TextEditingController txtId = TextEditingController();
TextEditingController txtName = TextEditingController();
TextEditingController txtLastName = TextEditingController();
TextEditingController txtAge = TextEditingController();
GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration();
QueryMutation addMutation = QueryMutation();

final Person person;
final bool isAdd;

_AlertDialogWindow(this.person, this.isAdd);

@override
void initState() {
super.initState();
if (!this.isAdd) {
txtId.text = person.getId();
txtName.text = person.getName();
txtLastName.text = person.getLastName();
txtAge.text = person.getAge().toString();
}
}

@override
Widget build(BuildContext context) {
// TODO: implement build
return AlertDialog(
title: Text(this.isAdd ? "Add" : "Edit or Delete"),
content: Container(
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width,
),
child: Stack(
children: <Widget>[
Container(
child: TextField(
maxLength: 5,
controller: txtId,
enabled: this.isAdd,
decoration: InputDecoration(
icon: Icon(Icons.perm_identity),
labelText: "ID",
),
),
),
Container(
padding: EdgeInsets.only(top: 80.0),
child: TextField(
maxLength: 40,
controller: txtName,
decoration: InputDecoration(
icon: Icon(Icons.text_format),
labelText: "Name",
),
),
),
Container(
padding: EdgeInsets.only(top: 160.0),
child: TextField(
maxLength: 40,
controller: txtLastName,
decoration: InputDecoration(
icon: Icon(Icons.text_rotate_vertical),
labelText: "Last name",
),
),
),
Container(
padding: EdgeInsets.only(top: 240.0),
child: TextField(
maxLength: 2,
controller: txtAge,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: "Age", icon: Icon(Icons.calendar_today)),
),
),
],
),
),
),
),
actions: <Widget>[
FlatButton(
child: Text("Close"),
onPressed: () => Navigator.of(context).pop(),
),
!this.isAdd
? FlatButton(
child: Text("Delete"),
onPressed: () async {
GraphQLClient _client = graphQLConfiguration.clientToQuery();
QueryResult result = await _client.mutate(
MutationOptions(
document: addMutation.deletePerson(txtId.text),
),
);
if (!result.hasErrors) Navigator.of(context).pop();
},
)
: null,
FlatButton(
child: Text(this.isAdd ? "Add" : "Edit"),
onPressed: () async {
if (txtId.text.isNotEmpty &&
txtName.text.isNotEmpty &&
txtLastName.text.isNotEmpty &&
txtAge.text.isNotEmpty) {
if (this.isAdd) {
GraphQLClient _client = graphQLConfiguration.clientToQuery();
QueryResult result = await _client.mutate(
MutationOptions(
document: addMutation.addPerson(
txtId.text,
txtName.text,
txtLastName.text,
int.parse(txtAge.text),
),
),
);
if (!result.hasErrors) {
txtId.clear();
txtName.clear();
txtLastName.clear();
txtAge.clear();
Navigator.of(context).pop();
}
} else {
GraphQLClient _client = graphQLConfiguration.clientToQuery();
QueryResult result = await _client.mutate(
MutationOptions(
document: addMutation.editPerson(
txtId.text,
txtName.text,
txtLastName.text,
int.parse(txtAge.text),
),
),
);
if (!result.hasErrors) {
txtId.clear();
txtName.clear();
txtLastName.clear();
txtAge.clear();
Navigator.of(context).pop();
}
}
}
},
)
],
);
}
}

In the _AlertDialog we used all the mutations created to add, edit and delete a person. The process is as follows:

  • We called our GraphQl configuration from GraphQLConfiguration: graphQLConfiguration = GraphQLConfiguration();
  • We get the mutations from the class we have created: QueryMutation addMutation = QueryMutation();
  • We create our GraphQL client to connect to our backend: GraphQLClient _client = graphQLConfiguration.clientToQuery();
  • Finally we do our mutation with the required parameters.

Once we have done the previous steps we check is our result is correct, and if that is true, just return to the main page and clear all the TextFields. Basically, all the queries and mutations done during the example are based under the same principle: declare your variables, do your query or mutation and finally check if that has errors.

Finally, we have our whole Principal widget, and there is something more we need to remark. Inside our initState() we are going to call all the data saved in our backend, and to do that we use our function fillList(), and basically it will fill a listPerson with all the data stored, to access to it, we do result.data[“name our query or mutation”][index][“name of the variable”] and that is it.

import "package:flutter/material.dart";
import "package:example/components/alertDialogs.dart";
import "package:graphql_flutter/graphql_flutter.dart";
import "package:example/components/person.dart";
import "../services/graphqlConf.dart";
import "../services/queryMutation.dart";

class Principal extends StatefulWidget {
@override
State<StatefulWidget> createState() => _Principal();
}

class _Principal extends State<Principal> {
List<Person> listPerson = List<Person>();
GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration();

void fillList() async {
QueryMutation queryMutation = QueryMutation();
GraphQLClient _client = graphQLConfiguration.clientToQuery();
QueryResult result = await _client.query(
QueryOptions(
document: queryMutation.getAll(),
),
);
if (!result.hasErrors) {
for (var i = 0; i < result.data["persons"].length; i++) {
setState(() {
listPerson.add(
Person(
result.data["persons"][i]["id"],
result.data["persons"][i]["name"],
result.data["persons"][i]["lastName"],
result.data["persons"][i]["age"],
),
);
});
}
}
}

@override
void initState() {
super.initState();
fillList();
}

void _addPerson(context) {
showDialog(
context: context,
builder: (BuildContext context) {
AlertDialogWindow alertDialogWindow =
new AlertDialogWindow(isAdd: true);
return alertDialogWindow;
},
).whenComplete(() {
listPerson.clear();
fillList();
});
}

void _editDeletePerson(context, Person person) {
showDialog(
context: context,
builder: (BuildContext context) {
AlertDialogWindow alertDialogWindow =
new AlertDialogWindow(isAdd: false, person: person);
return alertDialogWindow;
},
).whenComplete(() {
listPerson.clear();
fillList();
});
}

@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("Example"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add_circle_outline),
onPressed: () => _addPerson(context),
tooltip: "Insert new person",
),
],
),
body: Stack(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
child: Text(
"Person",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 30.0),
),
),
Container(
padding: EdgeInsets.only(top: 50.0),
child: ListView.builder(
itemCount: listPerson.length,
itemBuilder: (context, index) {
return ListTile(
selected: listPerson == null ? false : true,
title: Text(
"${listPerson[index].getName()}",
),
onTap: () {
_editDeletePerson(context, listPerson[index]);
},
);
},
),
),
],
),
);
}
}

Our result!


Build CRUD Mobile App using React Native, Elements, Navigation, Apollo Client and GraphQL

Build CRUD Mobile App using React Native, Elements, Navigation, Apollo Client and GraphQL

A comprehensive step by step tutorial on learn to build CRUD (Create, Read, Update, Delete) using React Native, Elements, Navigation, Apollo Client and GraphQL

A comprehensive step by step tutorial on learn to build CRUD (Create, Read, Update, Delete) using React Native, Elements, Navigation, Apollo Client and GraphQL

We will use existing Node.js, Express.js, MongoDB, and GraphQL that already created in React.js tutorial. So, we will focus on accessing GraphQL from the React Native Mobile Apps. Same exactly as React.js tutorial, we will use Apollo Client GraphQL to integrating with React Native.

The flow of CRUD Mobile Apps is very basic. It just the main page that shows the list of data, the details page, the edit, and the create data page. In this tutorial, we will use React Native and React Native Elements components including ScrollView, TextInput, Text, Button, View, etc. The list page contains a list of the book with the details button and a button to add new data on the right corner of the header. The details page contains data details text, the back button on the left corner of the header, an edit button and a delete button. The add-data page contains a React Native form, back button, and save button. The edit-data page contains the React Native form, back data, and update button. Simply as that, we will try to build this React Native Mobile Apps to Android and iOS devices.

The Example of React Native Navigation and Elements wrap together with Apollo Client GraphQL

The following tools, frameworks, and modules are required for this tutorial:

Before start to the main steps, make sure that you have installed Node.js and can run npm in the terminal or command line. To check the existing or installed Node.js environment open the terminal/command line then type this command.

node -v
v10.15.1
npm -v
6.9.0
yarn -v
1.10.1

React Native and Apollo GraphQL Tutorial: Install create-react-native-app and Create App

The create-react-native-app is a tool for creating a React Native App. To install it, type this command in your App projects folder.

sudo npm install -g create-react-native-app

Then create a React Native App using this create-react-native-app command.

create-react-native-app RnGraphql

If there question to install Expo, just type Y and choose a blank template because we will add the navigation later. That command will create a React Native app then install all required modules. The app or project folder will contain these folders and files.

|-- App.js
|-- app.json
|-- assets
| |-- icon.png
| `-- splash.png
|-- node_modules
`-- package.json

Next, go to the newly created React App folder.

cd RnGraphql

This React Native App is running via Expo app, before running on your Android or iOS device, make sure you have to install the Expo App to Android or Expo Client to iOS. Of course, that app is available in the App Store. So, we assume that you have installed the Expo App in your device then type this command to run the app.

npm start
or
yarn start

You will see this barcode and instruction in the terminal or command line.

To open the app in the Android device, open the Expo App first then tap on Scan QR Code button. Scan the barcode in the terminal or command line then you will see the React Native Android App like this after waiting for minutes the build process.

For iOS Device, press s from the keyboard to send React Native App URL to your Email or SMS. Enter your phone number or Email address (We use an email address) then press Enter. You will get this email to your mailbox.

Choose open in Expo URL then open in your browser, that will be redirected to Expo App. In Expo App welcome screen, shake your phone to open the React Native App. Now, you will see this screen in your iOS device.

Make sure you have the same network between your device and your development computer.

React Native and Apollo GraphQL Tutorial: Add Navigation Header and required Screen

As you see, the React Native app that you can only show plain text which unlike the standard Android or iOS Mobile Apps. To make a standard Android or iOS Mobile Apps, we need to add React Native header and navigation button with an icon to the React Native view. Type this command to install React Native Navigation (react-navigation).

yarn add react-navigation

Also, add the React Native Gesture Handler module by type this command.

yarn add react-native-gesture-handler

We have to link react-native to react-native-gesture-handler.

react-native link react-native-gesture-handler

Next, create a folder for components and components files in the root of the app folder by type these commands.

mkdir components
touch components/BooksScreen.js
touch components/BookDetailScreen.js
touch components/AddBookScreen.js
touch components/EditBookScreen.js

We will fill that files with simple React Native Button, Text, View, props, and navigation. Open and edit components/BooksScreen.js then add this React codes.

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';

class BooksScreen extends Component {
 static navigationOptions = {
  title: 'Books List',
 };
 render() {
  return (
   <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
    <Text>Books List</Text>
    <Button
     title="Go to Details"
     onPress={() => this.props.navigation.navigate('BookDetails')}
    />
    <Button
     title="Go to Add Book"
     onPress={() => this.props.navigation.navigate('AddBook')}
    />
    <Button
     title="Go to Edit Book"
     onPress={() => this.props.navigation.navigate('EditBook')}
    />
   </View>
  );
 }
}

export default BooksScreen;

Open and edit components/BookDetailScreen.js then add this React codes.

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';

class BookDetailScreen extends Component {
 static navigationOptions = {
  title: 'Book Details',
 };
 render() {
  return (
   <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
    <Text>Book Details</Text>
    <Button
     title="Go to Details... again"
     onPress={() => this.props.navigation.push('BookDetails')}
    />
    <Button
     title="Go to Home"
     onPress={() => this.props.navigation.navigate('Book')}
    />
    <Button
     title="Go back"
     onPress={() => this.props.navigation.goBack()}
    />
   </View>
  );
 }
}

export default BookDetailScreen;

Open and edit components/AddBookScreen.js then add this React codes.

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';

class AddBookScreen extends Component {
 static navigationOptions = {
  title: 'Add Book',
 };
 render() {
  return (
   <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
    <Text>Add Book</Text>
    <Button
     title="Go to Add Book... again"
     onPress={() => this.props.navigation.push('AddBook')}
    />
    <Button
     title="Go to Home"
     onPress={() => this.props.navigation.navigate('Book')}
    />
    <Button
     title="Go back"
     onPress={() => this.props.navigation.goBack()}
    />
   </View>
  );
 }
}

export default AddBookScreen;

Open and edit components/EditBookScreen.js then add this React codes.

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';

class EditBookScreen extends Component {
 static navigationOptions = {
  title: 'Edit Book',
 };
 render() {
  return (
   <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
    <Text>Add Book</Text>
    <Button
     title="Go to Edit Book... again"
     onPress={() => this.props.navigation.push('EditBook')}
    />
    <Button
     title="Go to Home"
     onPress={() => this.props.navigation.navigate('Book')}
    />
    <Button
     title="Go back"
     onPress={() => this.props.navigation.goBack()}
    />
   </View>
  );
 }
}

export default EditBookScreen;

Next, open and edit App.js then add replace all codes with this.

import { createStackNavigator, createAppContainer } from 'react-navigation';
import BooksScreen from './components/BooksScreen';
import BookDetailScreen from './components/BookDetailScreen';
import AddBookScreen from './components/AddBookScreen';
import EditBookScreen from './components/EditBookScreen';

const MainNavigator = createStackNavigator({
 Book: { screen: BooksScreen },
 BookDetails: { screen: BookDetailScreen },
 AddBook: { screen: AddBookScreen },
 EditBook: { screen: EditBookScreen },
});

const App = createAppContainer(MainNavigator);

export default App;

Next, run again the React Native app then refresh your Expo app. You will see the standard Android or iOS Mobile Apps in your device screen.

React Native and Apollo GraphQL Tutorial: Install and Configure Apollo Client GraphQL

Now, we have to install and configure all of the required modules and dependencies to the **React Apollo **and GraphQL. Type this command to install the modules at once.

yarn add react-apollo apollo-client apollo-cache-inmemory apollo-link-http graphql-tag graphql

Next, open and edit App.js then replace all imports with these imports.

import React from 'react';
import { AppRegistry } from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';
import BooksScreen from './components/BooksScreen';
import BookDetailScreen from './components/BookDetailScreen';
import AddBookScreen from './components/AddBookScreen';
import EditBookScreen from './components/EditBookScreen';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { ApolloProvider } from 'react-apollo';

Change the constant name of createAppContainer constant variable.

const MyRootComponent = createAppContainer(MainNavigator);

Add these lines of Apollo Client constant variables.

const cache = new InMemoryCache();
const client = new ApolloClient({
 cache,
 link: new HttpLink({
    uri: 'http://localhost:3000/graphql',
 }),
});

const App = () => (
 <ApolloProvider client={client}>
  <MyRootComponent />
 </ApolloProvider>
);

Register the Apollo Provider by adding this line before the export line.

AppRegistry.registerComponent('MyApp', () => App);

React Native and Apollo GraphQL Tutorial: Show List of Books

To show the list of the books in the Books components, open and edit components/BooksScreen.js then add/replace these imports.

import React, { Component } from 'react';
import { StyleSheet, FlatList, ActivityIndicator, View, Text } from 'react-native';
import { ListItem, Button } from 'react-native-elements';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';

As you can see, there are view elements that use react-native-elements module. For that, install the react-native-elements module first by type this command.

yarn add react-native-elements

Declare a constant before the class name for the query.

const GET_BOOKS = gql`
 {
  books {
   _id
   title
   author
  }
 }
`;

Replace navigationOptions with this.

static navigationOptions = ({ navigation }) => {
  return {
    title: 'LIST OF BOOKS',
    headerRight: (
    <Button
      buttonStyle={{ padding: 0, backgroundColor: 'transparent' }}
      icon={{ name: 'add-circle', style: { marginRight: 0, fontSize: 28 } }}
      onPress={() => { navigation.push('AddBook') }}
    />
    ),
  };
};

Add the function to extract and mapping result from GraphQL and render list item from the FlatList that will be added to the render section.

keyExtractor = (item, index) => index.toString()

renderItem = ({ item }) => (
  <ListItem
    title={item.title}
    onPress={() => {
      this.props.navigation.navigate('BookDetails', {
        id: `${item._id}`,
      });
    }}
    chevron
    bottomDivider
  />
)

Replace all React Native view render with Apollo Client GraphQL Query with pollInterval to make the page always request GraphQL data.

render() {
  return (
    <Query pollInterval={500} query={GET_BOOKS}>
      {({ loading, error, data }) => {
        if (loading) return(
          <View style={styles.activity}>
            <ActivityIndicator size="large" color="#0000ff"/>
          </View>
        );
        if (error) return(
          <View style={styles.activity}>
            <Text>`Error! ${error.message}`</Text>
          </View>
        );
        return (
          <FlatList
            keyExtractor={this.keyExtractor}
            data={data.books}
            renderItem={this.renderItem}
          />
        );
      }}
    </Query>
  );
}

Add React Native styles constant variables before the export code.

const styles = StyleSheet.create({
 container: {
   flex: 1,
   paddingBottom: 22
 },
 item: {
   padding: 10,
   fontSize: 18,
   height: 44,
 },
 activity: {
   position: 'absolute',
   left: 0,
   right: 0,
   top: 0,
   bottom: 0,
   alignItems: 'center',
   justifyContent: 'center'
 }
})

React Native and Apollo GraphQL Tutorial: Show a Book Details

To show book details, we have to modify components/BookDetailScreen.js then replace all imports with these imports.

import React, { Component } from 'react';
import { ScrollView, StyleSheet, ActivityIndicator, View, Text } from 'react-native';
import { Card, Button } from 'react-native-elements';
import gql from 'graphql-tag';
import { Query, Mutation } from 'react-apollo';

Declare the constant variables of the GraphQL gql or Queries.

const GET_BOOK = gql`
  query book($bookId: String) {
    book(id: $bookId) {
      _id
      isbn
      title
      author
      description
      published_year
      publisher
      updated_date
    }
  }
`;

const DELETE_BOOK = gql`
 mutation removeBook($id: String!) {
  removeBook(id:$id) {
   _id
  }
 }
`;

Modify the render section of the class to display a book details by implementing Apollo GraphQL Query and React Native ScrollView, Card, View, Button, Text, ActivityIndicator, etc.

class BookDetailScreen extends Component {
 static navigationOptions = {
  title: 'Book Details',
 };
 render() {
  const { navigation } = this.props;
  return (
    <Query pollInterval={500} query={GET_BOOK} variables={{ bookId: navigation.getParam('id') }}>
      {({ loading, error, data }) => {
        if (loading) return(<View style={styles.activity}>
          <ActivityIndicator size="large" color="#0000ff" />
         </View>);
        if (error) return(<Text>`Error! ${error.message}`</Text>);
        return (
          <ScrollView>
            <Card style={styles.container}>
              <View style={styles.subContainer}>
                <View>
                  <Text style={{fontSize: 16, fontWeight: 'bold'}}>ISBN:</Text>
                  <Text style={{fontSize: 18, marginBottom: 10}}>{data.book.isbn}</Text>
                </View>
                <View>
                  <Text style={{fontSize: 16, fontWeight: 'bold'}}>Title: </Text>
                  <Text style={{fontSize: 18, marginBottom: 10}}>{data.book.title}</Text>
                </View>
                <View>
                  <Text style={{fontSize: 16, fontWeight: 'bold'}}>Author: </Text>
                  <Text style={{fontSize: 18, marginBottom: 10}}>{data.book.author}</Text>
                </View>
                <View>
                  <Text style={{fontSize: 16, fontWeight: 'bold'}}>Description: </Text>
                  <Text style={{fontSize: 18, marginBottom: 10}}>{data.book.description}</Text>
                </View>
                <View>
                  <Text style={{fontSize: 16, fontWeight: 'bold'}}>Published Year: </Text>
                  <Text style={{fontSize: 18, marginBottom: 10}}>{data.book.published_year}</Text>
                </View>
                <View>
                  <Text style={{fontSize: 16, fontWeight: 'bold'}}>Publisher: </Text>
                  <Text style={{fontSize: 18, marginBottom: 10}}>{data.book.publisher}</Text>
                </View>
                <View>
                  <Text style={{fontSize: 16, fontWeight: 'bold'}}>Updated Date: </Text>
                  <Text style={{fontSize: 18}}>{data.book.updated_date}</Text>
                </View>
              </View>
              <Mutation mutation={DELETE_BOOK} key={data.book._id} onCompleted={() => navigation.goBack()}>
                {(removeBook, { loading2, error2 }) => (
                  <View style={styles.subContainer}>
                    <Button
                    style={styles.detailButton}
                    large
                    backgroundColor={'#CCCCCC'}
                    leftIcon={{name: 'edit'}}
                    title='Edit'
                    onPress={() => {
                      navigation.navigate('EditBook', { id: `${data.book._id}`, });
                    }} />
                    <Button
                    style={styles.detailButton}
                    large
                    backgroundColor={'#999999'}
                    color={'#FFFFFF'}
                    leftIcon={{name: 'delete'}}
                    title='Delete'
                    onPress={() => {
                      removeBook({ variables: { id: data.book._id } })
                      .then(res => res)
                      .catch(err => <Text>{err}</Text>);
                    }} />
                    {loading2 && <View style={styles.activity}>
                      <ActivityIndicator size="large" color="#0000ff" />
                     </View>}
                    {error2 && <Text>`Error! ${error2.message}`</Text>}
                  </View>
                )}
              </Mutation>
            </Card>
          </ScrollView>
        );
      }}
    </Query>
  );
 }
}

Modify or add the React Native style to match the modified details page.

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20
  },
  subContainer: {
    flex: 1,
    paddingBottom: 20,
    borderBottomWidth: 2,
    borderBottomColor: '#CCCCCC',
  },
  activity: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
    alignItems: 'center',
    justifyContent: 'center'
  },
  detailButton: {
    marginTop: 10
  }
})

React Native and Apollo GraphQL Tutorial: Add a New Book

To add a new book, we have an add button in the List of Book page that will push the Add Page. This Add Page will use Apollo GraphQL Mutation instead of React Form. The React Native TextInput change will use a function that triggers the OnChange event. Open and edit components/AddBookScreen.js then replace all imports with these imports.

import React, { Component } from 'react';
import { StyleSheet, ScrollView, ActivityIndicator, View, TextInput, Text } from 'react-native';
import { Button } from 'react-native-elements';
import gql from "graphql-tag";
import { Mutation } from "react-apollo";

Declare a constant variable of the Apollo GraphQL gql or Query before the class name.

const ADD_BOOK = gql`
  mutation AddBook(
    $isbn: String!,
    $title: String!,
    $author: String!,
    $description: String!,
    $publisher: String!,
    $published_year: Int!) {
    addBook(
      isbn: $isbn,
      title: $title,
      author: $author,
      description: $description,
      publisher: $publisher,
      published_year: $published_year) {
      _id
    }
  }
`;

Add an object of the state after the navigationOptions that consists of required fields that match the GraphQL data.

state = {
 isbn: '',
 title: '',
 author: '',
 description: '',
 published_year: '',
 publisher: '',
}

Add a function to update the React Native TextInput value and state object values.

updateTextInput = (text, field) => {
 const state = this.state
 state[field] = text;
 this.setState(state);
}

Modify the React Native render section to add Apollo GraphQL Mutation, React Native TextInput, ScrollView, View, ActivityIndicator, etc.

render() {
  const { isbn, title, author, description, published_year, publisher } = this.state;
  return (
   <Mutation mutation={ADD_BOOK} onCompleted={() => this.props.navigation.goBack()}>
     {(addBook, { loading, error }) => (
      <ScrollView style={styles.container}>
       <View style={styles.subContainer}>
        <TextInput
          style={styles.textInput}
          placeholder={'ISBN'}
          value={this.state.isbn}
          onChangeText={(text) => this.updateTextInput(text, 'isbn')}
        />
       </View>
       <View style={styles.subContainer}>
        <TextInput
          style={styles.textInput}
          placeholder={'Title'}
          value={this.state.title}
          onChangeText={(text) => this.updateTextInput(text, 'title')}
        />
       </View>
       <View style={styles.subContainer}>
        <TextInput
          style={styles.textInput}
          placeholder={'Author'}
          value={this.state.author}
          onChangeText={(text) => this.updateTextInput(text, 'author')}
        />
       </View>
       <View style={styles.subContainer}>
        <TextInput
          style={styles.textInput}
          placeholder={'Description'}
          multiline={true}
          numberOfLines={4}
          value={this.state.description}
          onChangeText={(text) => this.updateTextInput(text, 'description')}
        />
       </View>
       <View style={styles.subContainer}>
        <TextInput
          style={styles.textInput}
          placeholder={'Published Year'}
          value={this.state.published_year}
          keyboardType='numeric'
          onChangeText={(text) => this.updateTextInput(text, 'published_year')}
        />
       </View>
       <View style={styles.subContainer}>
        <TextInput
          style={styles.textInput}
          placeholder={'Publisher'}
          value={this.state.publisher}
          onChangeText={(text) => this.updateTextInput(text, 'publisher')}
        />
       </View>
       <View>
        <Button
         large
         leftIcon={{name: 'save'}}
         title='Save'
         onPress={() => {
          addBook({
           variables: {
            isbn: this.state.isbn,
            title: this.state.title,
            author: this.state.author,
            description: this.state.description,
            publisher: this.state.publisher,
            published_year: parseInt(this.state.published_year),
           }
          })
           .then(res => this.setState({ isbn: '', title: '', author: '', description: '', published_year: '', publisher }))
           .catch(err => <Text>{err}</Text>);
         }} />
       </View>
       {loading && <View style={styles.activity}>
         <ActivityIndicator size="large" color="#0000ff" />
        </View>}
       {error && <Text>`Error! ${error.message}`</Text>}
      </ScrollView>
     )}
    </Mutation>
  );
 }
}

Also, modify the React Native style to match the required views.

const styles = StyleSheet.create({
 container: {
  flex: 1,
  padding: 20
 },
 subContainer: {
  flex: 1,
  marginBottom: 20,
  padding: 5,
  borderBottomWidth: 2,
  borderBottomColor: '#CCCCCC',
 },
 activity: {
  position: 'absolute',
  left: 0,
  right: 0,
  top: 0,
  bottom: 0,
  alignItems: 'center',
  justifyContent: 'center'
 },
 textInput: {
  fontSize: 18,
  margin: 5,
 },
})

React Native and Apollo GraphQL Tutorial: Edit a Book

The Edit Book Screen pushed from the Book Details Screen. Same as the previous Add Book Page, this page will contain the Apollo GraphQL Mutation and React Native TextInput with additional Apollo GraphQL Query. All React Native TextInput will fill with the data from GraphQL Query. Open and edit components/EditBookScreen.js then replace all imports with these imports.

import React, { Component } from 'react';
import { StyleSheet, ScrollView, ActivityIndicator, View, TextInput, Text } from 'react-native';
import { Button } from 'react-native-elements';
import gql from "graphql-tag";
import { Query, Mutation } from "react-apollo";

Declare the constant variables of the Apollo GraphQL Query or gql after the imports.

const GET_BOOK = gql`
  query book($bookId: String) {
    book(id: $bookId) {
      _id
      isbn
      title
      author
      description
      published_year
      publisher
      updated_date
    }
  }
`;

const UPDATE_BOOK = gql`
  mutation updateBook(
    $id: String!,
    $isbn: String!,
    $title: String!,
    $author: String!,
    $description: String!,
    $publisher: String!,
    $published_year: Int!) {
    updateBook(
    id: $id,
    isbn: $isbn,
    title: $title,
    author: $author,
    description: $description,
    publisher: $publisher,
    published_year: $published_year) {
      updated_date
    }
  }
`;

The above constant variables are Apollo GraphQL Query of the GET and the UPDATE Book data. Next, inside the main class, add an object of the state that contains required fields.

state = {
 isbn: '',
 title: '',
 author: '',
 description: '',
 published_year: '',
 publisher: '',
}

Add a function to update only the state instead of the state and the fields because the fields will fill with the data from GraphQL. In this case, you can't call setState twice to fill the state, so, we fill the state and the fields separately.

updateTextInput = (text, field) => {
 const state = this.state
 state[field] = text;
 this.setState(state);
}

Modify the React Native render section to Apollo GraphQL Query, Mutation, React Native ScrollView, View, TextInput, Button, etc.

render() {
  const { navigation } = this.props;
  const { isbn, title, author, description, published_year, publisher } = this.state;
  return (
   <Query query={GET_BOOK} variables={{ bookId: navigation.getParam('id') }}>
    {({ loading, error, data }) => {
     if (loading) return(<View style={styles.activity}>
       <ActivityIndicator size="large" color="#0000ff" />
      </View>);
     if (error) return(<Text>`Error! ${error.message}`</Text>);
     return (
      <Mutation mutation={UPDATE_BOOK} key={data.book._id} onCompleted={() => navigation.goBack()}>
       {(updateBook, { loading2, error2 }) => (
        <ScrollView style={styles.container}>
         <View style={styles.subContainer}>
          <TextInput
            style={styles.textInput}
            placeholder={'ISBN'}
            defaultValue={data.book.isbn}
            onChangeText={(text) => this.updateTextInput(text, 'isbn')}
          />
         </View>
         <View style={styles.subContainer}>
          <TextInput
            style={styles.textInput}
            placeholder={'Title'}
            defaultValue={data.book.title}
            onChangeText={(text) => this.updateTextInput(text, 'title')}
          />
         </View>
         <View style={styles.subContainer}>
          <TextInput
            style={styles.textInput}
            placeholder={'Author'}
            defaultValue={data.book.author}
            onChangeText={(text) => this.updateTextInput(text, 'author')}
          />
         </View>
         <View style={styles.subContainer}>
          <TextInput
            style={styles.textInput}
            placeholder={'Description'}
            multiline={true}
            numberOfLines={4}
            defaultValue={data.book.description}
            onChangeText={(text) => this.updateTextInput(text, 'description')}
          />
         </View>
         <View style={styles.subContainer}>
          <TextInput
            style={styles.textInput}
            placeholder={'Published Year'}
            defaultValue={data.book.published_year.toString()}
            keyboardType='numeric'
            onChangeText={(text) => this.updateTextInput(text, 'published_year')}
          />
         </View>
         <View style={styles.subContainer}>
          <TextInput
            style={styles.textInput}
            placeholder={'Publisher'}
            defaultValue={data.book.publisher}
            onChangeText={(text) => this.updateTextInput(text, 'publisher')}
          />
         </View>
         <View>
          <Button
           large
           leftIcon={{name: 'save'}}
           title='Save'
           onPress={() => {
            if (this.state.isbn === '')
             this.state.isbn = data.book.isbn;
            if (this.state.title === '')
             this.state.title = data.book.title;
            if (this.state.author === '')
             this.state.author = data.book.author;
            if (this.state.description === '')
             this.state.description = data.book.description;
            if (this.state.publisher === '')
             this.state.publisher = data.book.publisher;
            if (this.state.published_year === '')
             this.state.published_year = data.book.published_year;
            updateBook({
             variables: {
              id: data.book._id,
              isbn: this.state.isbn,
              title: this.state.title,
              author: this.state.author,
              description: this.state.description,
              publisher: this.state.publisher,
              published_year: parseInt(this.state.published_year),
             }
            })
             .then(res => res)
             .catch(err => <Text>{err}</Text>);
           }} />
         </View>
         {loading2 && <View style={styles.activity}>
           <ActivityIndicator size="large" color="#0000ff" />
          </View>}
         {error2 && <Text>`Error! ${error2.message}`</Text>}
        </ScrollView>
       )}
      </Mutation>
     );
    }}
   </Query>
  );
 }
}

Modify the React Native style to match the view requirements.

const styles = StyleSheet.create({
 container: {
  flex: 1,
  padding: 20
 },
 subContainer: {
  flex: 1,
  marginBottom: 20,
  padding: 5,
  borderBottomWidth: 2,
  borderBottomColor: '#CCCCCC',
 },
 activity: {
  position: 'absolute',
  left: 0,
  right: 0,
  top: 0,
  bottom: 0,
  alignItems: 'center',
  justifyContent: 'center'
 },
 textInput: {
  fontSize: 18,
  margin: 5,
 },
})

React Native and Apollo GraphQL Tutorial: Run and Test the Mobile Apps

Before running the React Native apps, we have to download and run the Node.js, Express.js, MongoDB GraphQL server. After download and install the required Node modules, run first the MongoDB server in the different terminal tab.

mongod

Run the Express.js GraphQL server in another terminal tab.

nodemon

In the current React Native app terminal tab, run the React Native app.

yarn start

Open again the Expo app on your iOS or Android device then refresh the current running React Native application. And here they are the full React Native Mobile Apps running with Apollo GraphQL data.

That it's, the React Native and Apollo GraphQL Tutorial: Build Mobile Apps with React Native Elements and Navigation. You can find the full source code from our GitHub.

Getting started with GraphQL and TypeScript

Getting started with GraphQL and TypeScript

In this tutorial, I’ll be showing you how to use TypeScript with GraphQL using TypeGraphQL.

TypeScript is a superset of JavaScript, and its adoption has skyrocketed in recent years, as many apps are now being rewritten in it. If you have ever created a GraphQL server with TypeScript, then you would know it’s not as straightforward as in the JavaScript counterpart.


Prerequisites

This tutorial assumes the following:

  • Node.js and NPM installed on your computer
  • Basic knowledge of GraphQL
  • Basic knowledge of TypeScript
  • TypeScript installed on your computer, which you can get from the official website
What’s TypeGraphQL?

TypeGraphQL is a framework building GraphQL APIs in Node.js. It makes use of TypeScript classes and decorators for defining GraphQL schema and types as well as resolvers. With TypeGraphQL, we don’t need to manually define types in SDL or create interfaces for our GraphQL schema. TypeGraphQL allows us to have only one source of truth, that way reducing field type mismatches, typos etc.

Another interesting thing about TypeGraphQL is how well it integrates with decorator-based libraries, like TypeORM, sequelize-typescript or Typegoose. This allows us to define both the GraphQL type and the entity in a single class, so we don’t need to edit multiple files to add or rename some properties.


Getting started

To get started with TypeGraphQL, we need to first install it along with its dependencies. We’ll start by creating a new project:

    $ mkdir graphql-typescript
    $ cd graphql-typescript
    $ npm init -y

Then we install TypeGraphQL:

    $ npm install type-graphql

Next, we need to install TypeScript as a dev-dependency as well as types for Node.js:

    $ npm install typescript @types/node --save-dev

TypeGraphQL requires the reflect-metadata shim, so we need to install that as well:

    $ npm install reflect-metadata

Next, we need to define some TypeScript configurations for our project. Create a tsconfig.json file within the project’s root directory, and paste the snippet below into it:

    // tsconfig.json

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "lib": ["dom", "es2016", "esnext.asynciterable"],
    "moduleResolution": "node",
    "outDir": "./dist",
    "strict": true,
    "strictPropertyInitialization": false,
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  },
  "include": ["./src/**/*"]
}

If you have ever worked with TypeScript before (which this tutorial assumes), then you should be familiar with some of the settings above. Since TypeGraphQL makes extensive use of decorators, which are an experimental feature of TypeScript, we need to set both emitDecoratorMetadata and experimentalDecorators as true. Also, we need to add esnext.asynciterable to the list of library files, since graphql-subscription uses AsyncIterator.


Defining the GraphQL schema

We can start defining the schema for our GraphQL server. Create a new src directory, then within it, create a new schemas directory. Inside the schemas directory, create a Project.ts file and add the following code in it:

    // src/schemas/Project.ts

import { Field, Int, ObjectType } from "type-graphql";
import Task from "./Task";

@ObjectType()
export default class Project {
  @Field(type =&gt; Int)
  id: number;

  @Field()
  name: string;

  @Field(type =&gt; [Task])
  tasks: Task[];
}

We define a Project class and use the @ObjectType() decorator to define it as a GraphQL type. The Project type has three fields: id, name and tasks. We use the @Field decorator to define these fields. The @Field decorator can also accept optional arguments. We can pass to it the type the field should be or an object containing other options we want for the field. We explicitly set the type of the id field to be Int while tasks is an array of the type Task (which we’ll create shortly).

Next, let’s define the schema for the Task type. Inside the schemas directory, create a Task.ts file and add the following code in it:

    // src/schemas/Task.ts

import { Field, Int, ObjectType } from "type-graphql";
import Project from "./Project";

@ObjectType()
export default class Task {
  @Field(type =&gt; Int)
  id: number;

  @Field()
  title: string;

  @Field(type =&gt; Project)
  project: Project;

  @Field()
  completed: boolean;
}

This is pretty similar to the Project schema. With our schema defined, we can move on to creating the resolvers.


Adding sample data

Before we get to the resolvers, let’s quickly define some sample data we’ll be using to test out our GraphQL server. Create a data.ts file directly inside the src directory, and paste the snippet below into it:

    // src/data.ts

export interface ProjectData {
  id: number;
  name: string;
}

export interface TaskData {
  id: number;
  title: string;
  completed: boolean;
  project_id: number;
}

export const projects: ProjectData[] = [
  { id: 1, name: "Learn React Native" },
  { id: 2, name: "Workout" },
];

export const tasks: TaskData[] = [
  { id: 1, title: "Install Node", completed: true, project_id: 1 },
  { id: 2, title: "Install React Native CLI:", completed: false, project_id: 1},
  { id: 3, title: "Install Xcode", completed: false, project_id: 1 },
  { id: 4, title: "Morning Jog", completed: true, project_id: 2 },
  { id: 5, title: "Visit the gym", completed: false, project_id: 2 },
];

Creating the resolvers

Create a new resolvers directory inside the src directory. Inside the resolvers directory, create a ProjectResolver.ts file and paste the code below in it:

    // src/resolvers/ProjectResolver.ts

import { Arg, FieldResolver, Query, Resolver, Root } from "type-graphql";
import { projects, tasks, ProjectData } from "../data";
import Project from "../schemas/Project";

@Resolver(of =&gt; Project)
export default class {
  @Query(returns =&gt; Project, { nullable: true })
  projectByName(@Arg("name") name: string): ProjectData | undefined {
    return projects.find(project =&gt; project.name === name);
  }

  @FieldResolver()
  tasks(@Root() projectData: ProjectData) {
    return tasks.filter(task =&gt; {
      return task.project_id === projectData.id;
    });
  }
}

We use the @Resolver() decorator to define the class as a resolver, then pass to the decorator that we want it to be of the Project type. Then we create our first query, which is projectByName, using the @Query() decorator. The @Query decorator accepts two arguments: the return type of the query and an object containing other options which we want for the query. In our case, we want the query to return a Project and it can return null as well. The projectByName query accepts a single argument (name of the project), which we can get using the @Arg decorator. Then we use find() on the projects array to find a project by its name and simply return it.

Since the Project type has a tasks field, which is a custom field, we need to tell GraphQL how to resolve the field. We can do that using the @FieldResolver() decorator. We are getting the object that contains the result returned from the root or parent field (which will be the project in this case) using the @Root() decorator.

In the same vein, let’s create the resolvers for the Task type. Inside the resolvers directory, create a TaskResolver.ts file and paste the code below in it:

    // src/resolvers/TaskResolver.ts

import { Arg, FieldResolver, Mutation, Query, Resolver, Root } from "type-graphql";
import { projects, tasks, TaskData } from "../data";
import Task from "../schemas/Task";

@Resolver(of =&gt; Task)
export default class {
  @Query(returns =&gt; [Task])
  fetchTasks(): TaskData[] {
    return tasks;
  }

  @Query(returns =&gt; Task, { nullable: true })
  getTask(@Arg("id") id: number): TaskData | undefined {
    return tasks.find(task =&gt; task.id === id);
  }

  @Mutation(returns =&gt; Task)
  markAsCompleted(@Arg("taskId") taskId: number): TaskData {
    const task = tasks.find(task =&gt; {
      return task.id === taskId;
    });
    if (!task) {
      throw new Error(`Couldn't find the task with id ${taskId}`);
    }
    if (task.completed === true) {
      throw new Error(`Task with id ${taskId} is already completed`);
    }
    task.completed = true;
    return task;
  }

  @FieldResolver()
  project(@Root() taskData: TaskData) {
    return projects.find(project =&gt; {
      return project.id === taskData.project_id;
    });
  }
}

We define two queries: fetchTasks and getTask. The fetchTasks simply returns an array of all the tasks that have been created. The getTask query is pretty similar to the projectByName query. Then we define a mutation for marking a task as completed, using the @Mutation. This mutation will also return a Task. Firstly, we get the task that matches the supplied taskId. If we can’t find a match, we simply throw an appropriate error. If the task has already been marked as completed, again, we throw an appropriate error. Otherwise, we set the task completed value to true and lastly return the task.

Just as we did with the Project type, we define how we want to resolve the project field.


Building the GraphQL server

With everything in place, all that is left now is to tie them together by building a GraphQL server. We will be using graphql-yoga for building our GraphQL server. First, let’s install it:

    $ npm install graphql-yoga

With that installed, create an index.ts file directly inside the src directory, and paste the code below in it:

    // src/index.ts

import { GraphQLServer } from "graphql-yoga";
import "reflect-metadata";
import { buildSchema } from "type-graphql";
import ProjectResolver from "./resolvers/ProjectResolver";
import TaskResolver from "./resolvers/TaskResolver";

async function bootstrap() {
  const schema = await buildSchema({
    resolvers: [ProjectResolver, TaskResolver],
    emitSchemaFile: true,
  });

  const server = new GraphQLServer({
    schema,
  });

  server.start(() =&gt; console.log("Server is running on http://localhost:4000"));
}

bootstrap();

Since we need to build our schema first before making use of it in our GraphQL server, we create an async function, which we call bootstrap() (you can name it however you like). Using the buildSchema() from type-graphql, we pass to it our resolvers and we set emitSchemaFile to true (more on this shortly). Once the schema has been built, we instantiate a new GraphQL server and pass to it the schema. Then we start the server. Lastly, we call bootstrap().

Sometimes, we might need to see or inspect the schema in SDL (Schema Definition Language) that TypeGraphQL will generate for us. One way we can achieve that is setting emitSchemaFile to true at the point of building the schema. This will generate a schema.gql file directly in project’s root directory. Of course, we can customize the path however we want.

Note: make sure to import reflect-metadata on top of your entry file (before you use/import type-graphql or your resolvers)
Testing it out

Before we start testing our GraphQL, we need to first compile our TypeScript files to JavaScript. For that, we’ll be using the TypeScript compiler. Running the command below directly from the project’s root directory:

    $ tsc

The compiled JavaScript files will be inside the dist directory, as specified in tsconfig.json. Now we can start the GraphQL server:

    $ node ./dist/index.js

The server should be running on http://localhost:4000, and we can test it out with the following query:

    # fetch all tasks

{
  fetchTasks {
    title
    project {
      name
    }
  }
}


Conclusion

In this tutorial, we looked at what is TypeGraphQL and it makes it easy to work with GraphQL and TypeScript. To learn more about TypeGraphQL and other advanced features it provides, do check out their official website as well as the GitHub repo.

The complete code for this tutorial is available on GitHub.


Learn More

Secure Node.js, Express.js and PostgreSQL API using Passport.js

MEAN Stack Tutorial MongoDB, ExpressJS, AngularJS and NodeJS

Full Stack Developers: Everything You Need to Know

Machine Learning In Node.js With TensorFlow.js

Build a Simple Web App with Express, Angular, and GraphQL

Build a Basic App with Spring Boot and JPA using PostgreSQL

The Complete Python & PostgreSQL Developer Course

SQL & Database Design A-Z™: Learn MS SQL Server + PostgreSQL

The Complete SQL Bootcamp

Originally published by Chimezie Enyinnaya at https://pusher.com