What is GraphQL and how to use it

It is basically a query language for API's

What is GraphQL?

It is basically a query language for API's

GraphQL shows what are the different types of data provided by the server and then the client can pick exactly what it wants.

Also in GraphQL you can get multiple server resources in One call rather than making multiple REST API calls.

You can check https://graphql.org/ for the full list of benefits.

The thing is until you see GraphQL in action, it's hard to understand the benefits. So let's get started with using GraphQL.

We will be using GraphQL along with NodeJS in this Article.


Pre-requisites

Install NodeJS from https://nodejs.org/en/


How to use GraphQL with NodeJs

GraphQL can be used with multiple languages. Here we will focus on how we can use GraphQL with javascript using NodeJS.

Create a Folder called as graphql-with-nodejs. Go into the project folder and run npm init to create the NodeJS project. The command for this is given below.

cd graphql-with-nodejs
npm init
Install the Dependencies

Install Express using the following command

npm install express

Install GraphQL using the following command. We will be installing graphql and graphql for express.

npm install express-graphql graphql


NodeJS Code

Create a file called as server.js inside the project and copy the following code into it

const express = require('express');
const port = 5000;
const app = express();

app.get('/hello', (req,res) => {
res.send("hello");
}
);

app.listen(port);
console.log(Server Running at localhost:${port});

The above code has a single http get end point called as /hello.

The end point is created using express.

Now let us modify this code to enable GraphQL.


Enabling GraphQL in the code

GraphQL will have a single url endpoint called as /graphql which will handle all requests.

Copy the following code into server.js

//get all the libraries needed
const express = require('express');
const graphqlHTTP = require('express-graphql');
const {GraphQLSchema} = require('graphql');

const {queryType} = require('./query.js');

//setting up the port number and express app
const port = 5000;
const app = express();

// Define the Schema
const schema = new GraphQLSchema({ query: queryType });

//Setup the nodejs GraphQL server
app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true,
}));

app.listen(port);
console.log(GraphQL Server Running at localhost:${port});

Let us go through this code now

graphqlHTTP enables us to setup a GraphQL server at /graphql url. It basically knows how to handle the request that is coming in.

This setup is done in the following lines of code

app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true,
}));

Now let us explore the parameters inside graphqlHTTP


graphiql

graphiql is a Web UI using which you can test the graphql endpoints. We will set this to true so that it is easier to test the various graphql endpoints we create.


schema

Though graphql has only one external endpoint /graphql, this in turn can have multiple other endpoints doing various things. These endpoints would be specified in the schema.

The schema would do things like:

  • Specify the endpoints
  • Indicate the input and output fields for the endpoint
  • Indicate what action should be done when an endpoint is hit and so on.

The Schema is defined as follows in the code

const schema = new GraphQLSchema({ query: queryType });

The schema can contain query as well as mutation types. This article will focus only on the query type.


query

It is seen the in the schema that query has been set to queryType.

We import queryType from query.js file using the following command

const {queryType} = require('./query.js');

query.js is a custom file which we will be creating soon.

query is where we specify the read only endpoints in a schema.

Create a file called as query.js in the project and copy the following code into it.

const { GraphQLObjectType,
GraphQLString
} = require('graphql');

//Define the Query
const queryType = new GraphQLObjectType({
name: 'Query',
fields: {
hello: {
type: GraphQLString,

        resolve: function () {
            return "Hello World";
        }
    }
}

});

exports.queryType = queryType;

query Explained

queryType is created as a GraphQLObjectType and given the name Query.

fields is where we specify the various endpoints.

So here we are adding one endpoint called as hello

hello has a type of GraphQLString Which means this endpoint has a return type of String. The type is GraphQLString instead of String since this a graphql schema. So directly using String will not work.

resolve function indicates the action to be done when the endpoint is called. Here the action is to return a String "Hello World".

Finally we export the querytype using exports.queryType = queryType. This is to ensure we can import it in server.js


Running the Application

Run the application using the following command

node server.js

The application runs on localhost:5000/graphql.

You can test the application by going to localhost:5000/graphql.

This url runs the Graphiql web UI as shown in the screen below.

The input is given in the left and the output is shown in the right.

Give the following input

{
hello
}

This will give the following output

{
"data": {
"hello": "Hello World"
}
}
Congrats ūüėÉ

You have created your first GraphQL endpoint.


Adding more endpoints

We will create 2 new endpoints:

  • movie: This endpoint will return a movie, given the movie ID
  • director: This endpoint will return a director given the director ID. It will also return all the movies directed by this director.
Adding Data

Usually an application will read data from a Database. But for this tutorial we will be hardcoding the data in the code itself for simplicity.

Create a file called data.js and add the following code.

//Hardcode some data for movies and directors
let movies = [{
id: 1,
name: "Movie 1",
year: 2018,
directorId: 1
},
{
id: 2,
name: "Movie 2",
year: 2017,
directorId: 1
},
{
id: 3,
name: "Movie 3",
year: 2016,
directorId: 3
}
];

let directors = [{
id: 1,
name: "Director 1",
age: 20
},
{
id: 2,
name: "Director 2",
age: 30
},
{
id: 3,
name: "Director 3",
age: 40
}
];

exports.movies = movies;
exports.directors = directors;

This file has the movies and directors data. We will be using the data in this file for our endpoints.


Adding the movie endpoint to the query

The new endpoints will be added to queryType in query.js file

The code for the movie endpoint is shown below

movie: {
type: movieType,
args: {
id: { type: GraphQLInt }
},
resolve: function (source, args) {
return _.find(movies, { id: args.id });
}
}

The return type of this endpoint is movieType which we will be defining soon.

args parameter is used to indicate the input to the movie endpoint. The input to this endpoint is id which is of type

GraphQLInt

resolve function returns the movie corresponding to the id, from the movies list. find is a function from lodash library used to find an element in a list.

The complete code for query.js is shown below

const { GraphQLObjectType,
GraphQLString,
GraphQLInt
} = require('graphql');
const _ = require('lodash');

const {movieType} = require('./types.js');
let {movies} = require('./data.js');

//Define the Query
const queryType = new GraphQLObjectType({
name: 'Query',
fields: {
hello: {
type: GraphQLString,

        resolve: function () {
            return "Hello World";
        }
    },

    movie: {
        type: movieType,
        args: {
            id: { type: GraphQLInt }
        },
        resolve: function (source, args) {
            return _.find(movies, { id: args.id });
        }
    }
}

});

exports.queryType = queryType;

From the above code it can be seen that movieType is actually defined in types.js


Adding the Custom Type movieType

Create a file called as types.js.

Add the following code into types.js

const {
GraphQLObjectType,
GraphQLID,
GraphQLString,
GraphQLInt
} = require('graphql');

// Define Movie Type
movieType = new GraphQLObjectType({
name: 'Movie',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
year: { type: GraphQLInt },
directorId: { type: GraphQLID }

}

});

exports.movieType = movieType;

It can be seen that movieType is created as a GraphQLObjectType

It has 4 fields id, name, year and directorId. The types for each of these fields are specified as well while adding them.

These fields come directly from the data. In this case it will be from movies list.


Adding the query and type for director endpoint

Similar to movie, even the director endpoint can be added.

In query.js, the director endpoint can be added as follows

director: {
type: directorType,
args: {
id: { type: GraphQLInt }
},
resolve: function (source, args) {
return _.find(directors, { id: args.id });
}
}

directorType can be added as follows in types.js

//Define Director Type
directorType = new GraphQLObjectType({
name: 'Director',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
age: { type: GraphQLInt },
movies: {
type: new GraphQLList(movieType),
resolve(source, args) {
return _.filter(movies, { directorId: source.id });
}

    }

}

});

Wait a minute. The directorType is slightly differnt from movieType. Why is this?

why is there a resolve function inside directorType. Previously we saw that resolve functions were present only in the query


The Speciality of directorType

When the director endpoint is called we have to return the director details, as well as all the movies the director has directed.

The first 3 fields id,name,age in directorType are straightforward and come directly from the data ( directors list)

The fourth field movies needs to contain the list of movies by this director.

For this we are mentioning that the type of movies field is a

GraphQLList of movieType ( List of movies )

But how exactly will be find all the movies directed by this director?

For this we have a resolve function inside the movies field.

The inputs to this resolve function is source and args.

source will have the parent object details.

Lets say the fields id =1, name = "Random" and age = 20 for a director. Then source.id =1, source.name = "Random" and source.age = 20

So in this example, resolve function finds out all the movies where directorId matches the Id of the required Director.


Code

The Entire code for this application is available in this github repo


Testing the Application

Now Let us test the application for different scenarios.

Run the application using node server.js

Go to localhost:5000/graphql and try the following inputs.


movie

Input:

{
movie(id: 1) {
name
}
}

Output:

{
"data": {
"movie": {
"name": "Movie 1"
}
}
}

From the above it can be seen that, the client can request exactly what it wants and GraphQL will ensure only those parameters are sent back. Here only name field is requested and only that is sent back by the server.

In movie(id: 1), id is the input parameter. We are asking the server to send back the movie which has an id of 1.

Input:

{
movie(id: 3) {
name
id
year
}
}

Output:

{
"data": {
"movie": {
"name": "Movie 3",
"id": "3",
"year": 2016
}
}
}

In the above example name, id and year fields are requested. So the server sends back all of those fields.


director

Input:

{
director(id: 1) {
name
id,
age
}
}

Output:

{
"data": {
"director": {
"name": "Director 1",
"id": "1",
"age": 20
}
}
}

Input:

{
director(id: 1) {
name
id,
age,
movies{
name,
year
}
}
}

Output:

{
"data": {
"director": {
"name": "Director 1",
"id": "1",
"age": 20,
"movies": [
{
"name": "Movie 1",
"year": 2018
},
{
"name": "Movie 2",
"year": 2017
}
]
}
}
}

In the above example we see the power of GraphQL. We indicate we want a director with id 1. Also we indicate we want all the movies by this director. Both the director and movie fields are fully customizable and the client can request exactly what it wants.

Similarily this can be extended to other fields and types. For example we could run a query like Find a director with id 1. For this director find all the movies. For each of the movie find the actors. For each actor get the top 5 rated movies and so on. For this query we need to specify the relationship between the types. Once we do that, the client can query any relationship it wants.


Congrats ūüėÉ

You now know the basic concepts of GraphQL.

You can checkout the documentation to know more about GraphQL

Thanks for reading ‚̧

Follow me on Facebook | Twitter


Learn More

‚ėě The Modern GraphQL Bootcamp (Advanced Node.js)

‚ėě NodeJS - The Complete Guide (incl. MVC, REST APIs, GraphQL)

‚ėě GraphQL with React: The Complete Developers Guide

‚ėě Build a Simple Web App with Express, Angular, and GraphQL

‚ėě How to build GraphQL APIs with Kotlin, Spring Boot, and MongoDB?

‚ėě GraphQL API with AWS and Use with React

‚ėě GraphQL Tutorial: Understanding Spring Data JPA/SpringBoot

‚ėě Developing and Securing GraphQL APIs with Laravel

GraphQL Recipes - Building APIs with GraphQL Transform

GraphQL Recipes - Building APIs with GraphQL Transform

The GraphQL Transform library allows you to deploy AWS AppSync GraphQL APIs with features like NoSQL databases, authentication, ...

Originally published by Nader Dabit  at dev.to

To view the repo only containing the code and instructions for deploying these applications, click here.

In my post Infrastructure as Code in the Era of GraphQL and Full Stack Serverless I showed how you could leverage GraphQL, the AWS Amplify CLI, and the GraphQL Transform library to build, share, and deploy full stack cloud applications.

In this post I've created annotated GraphQL schemas that you can use to deploy popular types of applications.

The GraphQL Transform library allows you to deploy AWS AppSync GraphQL APIs with features like NoSQL databases, authentication, elasticsearch engines, lambda function resolvers, relationships, authorization, and more using GraphQL schema directives.

For example, take the following schema that is utilizing the @modeldirective:

type Note @model {
  id: ID!
  name: String!
  description: String
}

This schema will deploy the following:

  1. GraphQL API
  2. CRUD GraphQL operations for this base type: Create, Read, Update, Delete, and List
  3. GraphQL subscriptions (triggered by mutation events; create, update, delete)
  4. DynamoDB NoSQL Database
  5. GraphQL resolvers mapping the DynamoDB table to the GraphQL CRUD operations

As of this post there are 7 directives offered by the GraphQL Transform library:

@model
// Deploys DynamodDB + resolvers + additional GraphQL schema

@auth
// Allows the definition of auth rules and builds corresponding GraphQL resolvers based on these rules

@connection
// Enables you to specify relationships between @model object types

@searchable
// Handles streaming the data of an @model object type to Amazon Elasticsearch Service and configures search resolvers that search that information

@function
// Allows you to quickly & easily configure AWS Lambda resolvers within your AWS AppSync API

@key
// Enables you to configure custom index structures for @model types

@versioned
// Adds object versioning and conflict resolution to a type

Using this library you can deploy the back end for your application using only an annotated GraphQL schema.

In this post I will show example schemas that, when used with the Amplify GraphQL transform library, will build out the backends for many popular types of applications.

  1. Todo App
  2. Events App
  3. E Commerce App
  4. WhatsApp Clone
  5. Reddit Clone
  6. Chat App
  7. Instagram Clone
  8. Conference App
For a tutorial showing how to deploy these applications using the GraphQL transform library, check out the documentation here.
Some applications may require additional custom authorization logic for certain subscriptions that you may not want accessible to all users. To learn more, check out the documentation here.
Testing these out

To deploy any of these applications, run the following commands:

Be sure to first install the AWS Amplify CLI
$ amplify init

if the app needs auth

$ amplify add auth

$ amplify add api

> Choose GraphQL
> If using authentication, choose Amazon Cognito as authentication type
> Update GraphQL schema

if the app needs storage (i.e. images or video)

$ amplify add storage

$ amplify push

Testing locally

You can now use local mocking to test serverless GraphQL APIs, databases, and serverless functions locally.

$ amplify mock api

Check out this video for a quick overview of local testing:

Todo App

Let's start with something very basic: a Todo app.

This app has the following requirements. The user should be able to:

  1. List all Todos
  2. Create, update, and delete Todos

Based on these requirements we can assume we need the following for this application:

  1. Todo type
  2. Database
  3. GraphQL definition for mutations (create, update, delete todos)
  4. GraphQL definition for queries (listTodos)
  5. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

type Todo @model {
id: ID!
name: String!
description: String
}

This will deploy the entire GraphQL API including the DynamoDB NoSQL database, additional GraphQL schema for GraphQL CRUD and List operations, GraphQL subscriptions, and the GraphQL resolvers mapping the schema to the database.

Events App

Next, let's look at how we might create an events app. A point to notice here is that we will need to have a way for admins to be able to create, update, and delete events and guests to only be able to read events. We also want to be able to query and get a sorted list (by date) of the events.

Based on these requirements, the user should be able to:

  1. List events in order by date of event
  2. View individual event

An admin should also be able to:

  1. Create an event
  2. Update and delete an event

To build this app, we could use the following annotated GraphQL schema:

type Event
@model
@key(name: "queryName", fields: ["queryName", "time"], queryField: "eventsByDate")
@auth(rules: [{allow: groups, groups: ["Admin"], operations: [create, update, delete]}])
{
id: ID!
name: String!
description: String
time: String!
queryName: String!
}

When creating a new event, we would need to populate the queryNameparameter with a consistent value in order to be able to sort by time of the event:

mutation createEvent {
createEvent(input: {
name: "Rap battle of the ages"
description: "You don't want to miss this!"
time: "2018-07-13T16:00:00Z"
queryName: "Event"
}) {
id name description time
}
}
Note: because queryName will be the same ("Event") value across all items, you could also update the resolver request mapping template to auto-populate this value and therefore not need to specify it in the mutation.

Now, to query for a list of sorted events, you could use the following query:

query listEvents {
eventsByDate(queryName: "Event") {
items {
id
name
description
time
}
}
}

Once you've created the authentication by running amplify add auth, you can run amplify console auth to add a user to the Admin group or use a Lambda Trigger to do it automatically when certain users sign up.

E-commerce App

This app has the following requirements. The User should be able to:

  1. Create an account
  2. View products
  3. Purchase products
  4. View order / orders

An admin should be able to:

  1. Create, update, and delete products, orders, and users

Based on these requirements we can assume we need the following for this application:

  1. Product, Customer, and Order types
  2. Database
  3. GraphQL definition for mutations (create, update, delete products, customers, and orders)
  4. GraphQL definition for queries (get, list)
  5. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

type Customer
@model(subscriptions: null)
@auth(rules: [
{ allow: owner }, { allow: groups, groups: ["Admin"]}
]) {
id: ID!
name: String!
email: String!
address: String
}

type Product @model
@auth(rules: [
{allow: groups, groups: ["Admin"], operations: [create, update, delete]}
]) {
id: ID!
name: String!
description: String
price: Float!
image: S3Object
}

type S3Object {
bucket: String!
region: String!
key: String!
}

type Order @model
@auth(rules: [
{allow: owner}, {allow: groups, groups: ["Admin"]}
]) {
id: ID!
customer: Customer @connection
total: Float!
order: String
}

WhatsApp Clone

This app has the following requirements. The User should be able to:

  1. Create an account
  2. Update their profile with their avatar image
  3. Create a conversation
  4. Create a message in a conversation

Based on these requirements we can assume we need the following for this application:

  1. User, Conversation, and Message types
  2. Database
  3. GraphQL definition for mutations (create, update, delete users, conversations, and messages)
  4. GraphQL definition for queries, mutations, and subscriptions
  5. GraphQL subscriptions for real-time communication
  6. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

type User
@model
@auth(rules: [
{ allow: owner, ownerField: "id", operations: [create, update, delete] }
]) {
id: ID!
username: String!
avatar: S3Object
conversations: [ConvoLink] @connection(name: "UserLinks")
messages: [Message] @connection(name: "UserMessages")
createdAt: String
updatedAt: String
}

type Conversation
@model(subscriptions: null)
@auth(rules: [{ allow: owner, ownerField: "members" }]) {
id: ID!
messages: [Message] @connection(name: "ConvoMsgs", sortField: "createdAt")
associated: [ConvoLink] @connection(name: "AssociatedLinks")
name: String!
members: [String!]!
createdAt: String
updatedAt: String
}

type Message
@model(subscriptions: null, queries: null)
@auth(rules: [{ allow: owner, ownerField: "authorId" }]) {
id: ID!
author: User @connection(name: "UserMessages", keyField: "authorId")
authorId: String
content: String!
image: S3Object
conversation: Conversation! @connection(name: "ConvoMsgs", sortField: "createdAt")
messageConversationId: ID!
createdAt: String
updatedAt: String
}

type ConvoLink
@model(
mutations: { create: "createConvoLink", update: "updateConvoLink" }
queries: null
subscriptions: null
) {
id: ID!
user: User! @connection(name: "UserLinks")
convoLinkUserId: ID
conversation: Conversation! @connection(name: "AssociatedLinks")
convoLinkConversationId: ID!
createdAt: String
updatedAt: String
}

type Subscription {
onCreateConvoLink(convoLinkUserId: ID!): ConvoLink
@aws_subscribe(mutations: ["createConvoLink"])
onCreateMessage(messageConversationId: ID!): Message
@aws_subscribe(mutations: ["createMessage"])
}

type S3Object {
bucket: String!
region: String!
key: String!
}

Reddit Clone

This app has the following requirements. The User should be able to:

  1. Create an account
  2. Create & delete a post (post can be an image or text)
  3. Comment on a post
  4. Vote on a post
  5. Vote on a comment

Based on these requirements we can assume we need the following for this application:

  1. User, Post, Comment, and Vote types
  2. Database
  3. GraphQL definition for mutations (create, update, delete users, posts and comments)
  4. GraphQL definition for queries
  5. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

type User
@model
@auth(rules: [
{ allow: owner, ownerField: "id", operations: [create, update, delete] }
]) {
id: ID!
username: String!
posts: [Post] @connection
createdAt: String
updatedAt: String
}

type Post @model @auth(rules: [{allow: owner, operations: [create, update, delete]}]) {
id: ID!
postContent: String
postImage: S3Object
comments: [Comment] @connection
votes: Int
}

type Comment @model @auth(rules: [{allow: owner, operations: [create, update, delete]}]) {
id: ID!
text: String!
author: String!
votes: Int
post: Post @connection
}

type Vote @model
@key(name: "byUser", fields: ["createdBy", "createdAt"], queryField: "votesByUser")
{
id: ID!
postId: ID!
createdBy: ID!
createdAt: String!
vote: VoteType
}

type S3Object {
bucket: String!
region: String!
key: String!
}

input VoteInput {
type: VoteType!
id: ID!
}

enum VoteType {
up
down
}

Voting

For the voting mechanism to work, we need to make sure that the resolver is incrementing or decrementing the votes count directly at the DB level. To do that, you will need to create a new mutation and request mapping template to increment and decrement the vote on an item (comment or post).

First, add the following types to your GraphQL schema:

voteForPost(input: ItemVoteInput): Post

input ItemVoteInput {
type: ItemVoteType!
id: ID!
}

enum ItemVoteType {
up
down
}

Then create a custom resolver that would look something like this.

#set($vote = 1)
#if ($context.arguments.input.type == "down")
#set($vote = -1)
#end

{
"version" : "2017-02-28",
"operation" : "UpdateItem",
"key" : {
"id" : { "S" : "${context.arguments.input.id}" }
},
"update" : {
"expression" : "ADD votes :votes",
"expressionValues" : {
":votes" : { "N" : $vote }
}
}
}

Now, when creating a vote, you will need to do two things:

  1. Make sure that each vote made by the user has a unique id. To do that, you can use a combination of the user ID + the id of the item being voted on.
  2. Make sure the user has already not voted the same vote. To do so, you could query by id of the vote (userId + item ID), then if the item is there check to see if the vote is different. If it is, update it. If it's the same, then do nothing. If it doesn't exist, create the vote.

You also have the ability to query all votes from a user.

Chat App
Click here to view AWS AppSync Chat, a completed full-stack version of this app built with React.

This app has the following requirements. The User should be able to:

  1. Create an account
  2. Create a conversation
  3. Create a message in a conversation
  4. View a list of all conversations
  5. Have the ability to create a new conversation with another user

Based on these requirements we can assume we need the following for this application:

  1. User, Conversation, and Messsage types
  2. Database
  3. GraphQL definition for mutations (create, update, delete users, conversations and messages)
  4. GraphQL definition for queries
  5. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

type User
@model
@auth(rules: [
{ allow: owner, ownerField: "id", operations: [create, update, delete] }
]) {
id: ID!
username: String!
conversations: [ConvoLink] @connection(name: "UserLinks")
messages: [Message] @connection(name: "UserMessages")
createdAt: String
updatedAt: String
}

type Conversation
@model(subscriptions: null)
@auth(rules: [{ allow: owner, ownerField: "members" }]) {
id: ID!
messages: [Message] @connection(name: "ConvoMsgs", sortField: "createdAt")
associated: [ConvoLink] @connection(name: "AssociatedLinks")
name: String!
members: [String!]!
createdAt: String
updatedAt: String
}

type Message
@model(subscriptions: null, queries: null)
@auth(rules: [{ allow: owner, ownerField: "authorId" }]) {
id: ID!
author: User @connection(name: "UserMessages", keyField: "authorId")
authorId: String
content: String!
conversation: Conversation! @connection(name: "ConvoMsgs", sortField: "createdAt")
messageConversationId: ID!
createdAt: String
updatedAt: String
}

type ConvoLink
@model(
mutations: { create: "createConvoLink", update: "updateConvoLink" }
queries: null
subscriptions: null
) {
id: ID!
user: User! @connection(name: "UserLinks")
convoLinkUserId: ID
conversation: Conversation! @connection(name: "AssociatedLinks")
convoLinkConversationId: ID!
createdAt: String
updatedAt: String
}

type Subscription {
onCreateConvoLink(convoLinkUserId: ID!): ConvoLink
@aws_subscribe(mutations: ["createConvoLink"])
onCreateMessage(messageConversationId: ID!): Message
@aws_subscribe(mutations: ["createMessage"])
}

Instagram Clone

This app has the following requirements. The User should be able to:

  1. Create an account
  2. Create a post
  3. Create a comment on a post
  4. Follow and unfollow a user
  5. Like a comment or a post

Based on these requirements we can assume we need the following for this application:

  1. User, Post, Like, Following, and Comment types
  2. Database
  3. GraphQL definition for mutations (create, update, delete users, posts, comments, following, and likes)
  4. GraphQL definition for queries
  5. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

type User
@model
@auth(rules: [
{ allow: owner, ownerField: "id", operations: [create, update, delete] }
]) {
id: ID!
username: String!
posts: [Post] @connection
createdAt: String
updatedAt: String
}

type Post @model @auth(rules: [
{allow: owner, operations: [create, update, delete]}
]) {
id: ID!
postImage: S3Object!
comments: [Comment] @connection
likes: Int
}

type Comment @model @auth(rules: [{allow: owner, operations: [create, update, delete]}]) {
id: ID!
text: String!
author: String!
likes: Int
post: Post @connection
}

type Like @model
@key(name: "byUser", fields: ["createdBy", "createdAt"], queryField: "likesByUser")
{
id: ID!
postId: ID!
createdBy: ID!
createdAt: String!
liked: Boolean
}

type Following @model @key(name: "followerId", fields: ["followerId", "createdAt"], queryField: "listFollowing") {
id: ID
followerId: ID!
followingId: ID!
createdAt: String!
}

type S3Object {
bucket: String!
region: String!
key: String!
}

Likes

Similarly to the Reddit clone, we need to have some custom logic in our resolver to handle likes. In the case of Instagram, you probably only want a user to like a post a single time. If they tap like again, you may want to allow them to unlike a post.

We'll also probably want to keep a total count of likes for a post and for a comment. Luckily, this isn't that hard to accomplish. With DynamoDB (or any NoSQL database) we can do this directly in a database operation without having to first read from the database.

To implement the like count, we can first create the following GraphQL mutation definition:

likePost(like: Boolean!): Post
#set($like = true)
#if ($context.arguments.like == false")
#set($like = -1)
#end

{
"version" : "2017-02-28",
"operation" : "UpdateItem",
"key" : {
"id" : { "S" : "${context.arguments.input.id}" }
},
"update" : {
"expression" : "ADD likes :likes",
"expressionValues" : {
":likes" : { "N" : $like }
}
}
}

Conference App
Click here to view Conference App in a Box, a completed full-stack version of this app built with React Native.

This app has the following requirements. The User should be able to:

  1. Create an account
  2. View a list of talks
  3. View an individual talk
  4. Create a comment on a talk
  5. (optional) Report a comment

An Admin should be able to:

  1. Create, edit, and delete a talk

Based on these requirements we can assume we need the following for this application:

  1. Talk, Comment, and (optional) Report types
  2. Database
  3. GraphQL definition for mutations (create, update, delete talks, comments, and reports)
  4. GraphQL definition for queries
  5. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

type Talk @model
@auth(rules: [{allow: groups, groups: ["Admin"], operations: [create, update, delete]}])
{
id: ID!
name: String!
speakerName: String!
speakerBio: String!
time: String
timeStamp: String
date: String
location: String
summary: String!
twitter: String
github: String
speakerAvatar: String
comments: [Comment] @connection(name: "TalkComments")
}

type Comment @model {
id: ID!
talkId: ID
talk: Talk @connection(sortField: "createdAt", name: "TalkComments", keyField: "talkId")
message: String
createdAt: String
createdBy: String
deviceId: ID
}

type Report @model
@auth(rules: [
{allow: owner, operations: [create, update, delete]},
{allow: groups, groups: ["Admin"]}
])
{
id: ID!
commentId: ID!
comment: String!
talkTitle: String!
deviceId: ID
}

type ModelCommentConnection {
items: [Comment]
nextToken: String
}

type Query {
listCommentsByTalkId(talkId: ID!): ModelCommentConnection
}

type Subscription {
onCreateCommentWithId(talkId: ID!): Comment
@aws_subscribe(mutations: ["createComment"])
}

In this schema, notice we are adding an additional subscription to listen to new comments by ID. This way we can only subscribe to comments for the talk that we are currently viewing.

Originally published by Nader Dabit  at dev.to

========================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

‚ė쬆GraphQL with React: The Complete Developers Guide

‚ė쬆The Modern GraphQL Bootcamp (with Node.js and Apollo)

‚ė쬆Build a Realtime App with React Hooks and GraphQL

‚ė쬆Full-Stack React with GraphQL and Apollo Boost

Why should you choose GraphQL?

Why should you choose GraphQL?

Have you heard of GraphQL and wondering if it would be a good fit for your next project? In this post, we are going to understand what GraphQL is, and its benefits. ## Introduction to GraphQL If you are new to GraphQL, here is the long story...

Have you heard of GraphQL and wondering if it would be a good fit for your next project? In this post, we are going to understand what GraphQL is, and its benefits.

Introduction to GraphQL

If you are new to GraphQL, here is the long story short. GraphQL is the new specification that you can use for your API. It is an alternative to writing APIs using REST.

It was developed by Facebook as an internal solution for their mobile apps, and was later open sourced to the community. Ever since then, it has been widely popular among developers and has become a favorite solution for building services.

GraphQL is a query language and is agnostic of the language you use. It is just a specification. The client can query for the data they need from the server. And the server responds back with a JSON response to the query. The interesting thing to note here is that, the client can ask for exactly what they need, and they receive only that. Yes!

Why GraphQL?

Before picking any technology, we need to understand the reason behind choosing it. Let’s dive right it and understand why GraphQL can fit your development needs.

1. Superior Developer Experience

The biggest benefit of using GraphQL, is the superior developer experience that it provides in comparison to alternatives like REST. An interesting thing to note is that with GraphQL, frontend developers stand to benefit a ton. The client driven approach provides a lot of power to the clients to query for what they like by creating custom queries.

No versioning of APIs

This may come as a surprise to some developers. GraphQL has introduced developers to a whole new era in API design and development. Since the client only asks for the data that it needs, it has made API design using GraphQL very flexible. There is no versioning of APIs and adding new fields has no effect on the current client’s call to the APIs. What does this mean? Well, developers do not have to deal with the huge headache of maintaining multiple versions of the API.

The APIs can be continuously evolved without changing versions.

Modifications to GraphQL APIs are non-breaking changes. This principle is very desirable to developers, so that they can continue with their work with no hassle. GraphQL provides a higher level of flexibility in API development that developers have not seen before.

Teams work independently

With GraphQL, multiple teams can work independently in parallel. Have you been in a situation where the frontend team stalls their development, because they are waiting on the backend team to release the latest version of their API or finalize it? It is very common to run into situations where, the frontend/client teams are stalled while they wait on the final version of the API. This causes delays in development and leads to frustration.

With GraphQL, the front-end and back-end teams can work independently. With the strictly typed schema that GraphQL has, the teams can work in parallel. The front-end team can continue working with just a mock version of the API. They can also test the code with it. This gives the developers a pleasant experience without stalling their development work.

2. Declarative Data Fetching

One of the biggest perks of using GraphQL is its declarative data fetching approach.

GraphQL: No multiple rounds trips to server, no over-fetching and no under-fetching of data

Queries can be aggregated to ask for exactly what you want in one trip. With this approach, you can eliminate any over-fetching or under-fetching of data. You ask for what exactly you need and GraphQL gives you back what you asked for.

GraphQL Query in Action

Let’s try to understand this better with an example below. I want to build an app, that displays the blog author information. I want it to display the name of the author, the blog posts written by the author and the three most recent blog topics written by the author.

GraphQL Query Request

Our query will look like below:

 {
  author (id: 6) {
    name 
    posts (last: 3) {
      title
    }
    topics (last : 3) {
     name
    }
  }
}

We pass in the fields that we need response for. We are looking for the author’s name, last three blog posts written, and the last three topics they wrote. The query is structured to ask for exactly what we need.

GraphQL Query Response

Here is what we get back from the server in the form of a JSON response.

{
  "data" : {
    "author" : {
      "name" : "Adhithi Ravichandran",
      "posts" : [
        { title: "React vs. Vue : A Wholesome Comparison"},
        { title: "React Lifecycle Methods: A Deep Dive"},
        { title: "5 Essential Skills A Frontend Developer Should Possess"}
      ],
      "topics" : [
        { name: "React and Vue"},
        { name: "React"},
        { name: "General"}
      ]
    }
  }
}

We got a JSON response back from the server that has exactly what we asked for. It has returned the author name, the last three posts written by the author and the last three topics authored by author. Nothing more, nothing less and everything in a single trip to the server. Isn’t this amazing?!

3. Strongly Typed Schema

Another reason why you should use GraphQL is because of its strongly typed schema. We know that GraphQL is essentially a query language, and it is strongly typed. What this means is that the GraphQL schema should have types for all the objects that it uses. Thus, the schema acts as a contract between the client and the server. The example below shows how types are defined in a GraphQL schema.

type Person {
 id : ID
 name: String
 age: Int
 gender: String
}

We have defined a complex object Person of type _Person. _The object comprises of several fields, each with its own type.

The strongly typed GraphQL schema has several advantages.

  • Code is predictable
  • Schema acts as contract between client and server
  • Teams can work independently
  • Can detect errors early