6minutes Tech Graphql

6minutes Tech Graphql

Post Date : 2024-05-19T08:35:31+07:00

Modified Date : 2024-05-19T08:35:31+07:00

Category: cheatsheet 6minutes-tech

Tags:

The REST API issues

  1. Over fetching data
  • Getting back more data than we need

example.com/api/courses

[
    {
        "id": 1,
        "title": "6 minutes tech graphql",
        "author": "nextjsvietnam",
        "thumbnail_url": "...",
        "video_url": "...",
        ...
    }
]
  1. Under fetching data

example.com/api/courses/1

example.com/api/courses

{
  "id": 1,
  "title": "6 minutes tech graphql",
  "thumbnail_url": "..."
}

What is GraphQL?

  • A query language for your api
Query{
    books {
        title,
        author {
            name,
            id,
            courses {
                id,
                title,
                thumbnail
            }
        }
        price
    }
}
  • Single endpoint

example.com/graphql

Let’s getting start with ExpressJS

In this tutorial, we’ll install Apollo Server with ExpressJS. There is package on npm to allow us integrate apollo server with expressjs and middleware

package.json

{
  "name": "6minutes-tech-graphql",
  "version": "1.0.0",
  "description": "the 6 minutes tech graphql tutorial",
  "type": "module",
  "scripts": {
    "start": "node index.js",
    "dev": "node --watch index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "nextjsvietnam",
  "license": "ISC",
  "dependencies": {
    "@apollo/server": "^4.10.4",
    "body-parser": "^1.20.2",
    "cors": "^2.8.5",
    "express": "^4.19.2",
    "graphql": "^16.8.1"
  }
}
npm install @apollo/server graphql express cors body-parser --save
// index.js
import { ApolloServer } from "@apollo/server";
import { expressMiddleware } from "@apollo/server/express4";
import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHttpServer";
import express from "express";
import http from "http";
import cors from "cors";
import bodyParser from "body-parser";

const PORT = process.env.PORT || 1337;

// The GraphQL schema
const typeDefs = `#graphql
  type Query {
    hello: String
  }
`;

// A map of functions which return data for the schema.
const resolvers = {
  Query: {
    hello: () => "world",
  },
};

const app = express();
const httpServer = http.createServer(app);

// Set up Apollo Server
const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();

app.use(cors(), bodyParser.json(), expressMiddleware(server));

await new Promise((resolve) => httpServer.listen({ port: PORT }, resolve));
console.log(`🚀 Server ready at http://localhost:${PORT}`);
# this command will run the main entry point define in your package.json and watch changes
npm run dev

And you will see this

image

Okie, so already know what it is, the create a hello world graphql. Let’s dive in deeper.

The game review graphql api

The main components of GraphQL

image

  1. Schema
  • Built-in types: Int, Float, String, Boolean, ID
// schema.js
export const typeDefs = `#graphql
    type Game {
        id: ID! # not allow to be null
        title: String!
        platform: [String!]!
        reviews: [Review!]
    }
    type Review {
        id: ID!
        rating: Int!
        content: String!
        game: Game!
        author: Author!
    }
    type Author {
        id: ID!
        name: String!
        verified: Boolean!
        reviews: [Review!]
    }
    # it is mandatory, work as entry point
    # it works as a gateway keeper of your graph
    type Query {
        reviews: [Review]
        review(id: ID!): Review
        games: [Game]
        game(id: ID!): Game
        authors: [Author]
        author(id: ID!): Author
    }
    # Mutation
    type Mutation {
        addGame(game: AddGameInput!): Game
        deleteGame(id: ID!): [Game]
        updateGame(id: ID!, updateGameDto: EditGameInput!): Game
    }
    input AddGameInput {
        title: String!
        platform: [String!]!
    }
    input EditGameInput {
        title: String
        platform: [String!]
    }
`;
// Int, Float, String, Boolean, ID

Resolver

import { ApolloServer } from "@apollo/server";
import { expressMiddleware } from "@apollo/server/express4";
import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHttpServer";
import express from "express";
import http from "http";
import cors from "cors";
import bodyParser from "body-parser";

import { typeDefs } from "./schema.js";
import _db from "./_db.js";

const PORT = process.env.PORT || 1337;

// A map of functions which return data for the schema.
const resolvers = {
  Query: {
    games: () => {
      return _db.games;
    },
    game: (_, args, context) => {
      const id = args.id;
      return _db.games.find((game) => game.id === id);
    },
    authors: () => {
      return _db.authors;
    },
    author: (_, args, context) => {
      const id = args.id;
      return _db.authors.find((author) => author.id === id);
    },
    reviews: () => {
      return _db.reviews;
    },
    review: (_, args, context) => {
      const id = args.id;
      return _db.reviews.find((review) => review.id === id);
    },
  },
  Game: {
    reviews: (parent, args, context) => {
      const gameId = parent.id;
      return _db.reviews.filter((review) => review.game_id === gameId);
    },
  },
  Author: {
    reviews: (parent, args, context) => {
      const authorId = parent.id;
      return _db.reviews.filter((review) => review.author_id === authorId);
    },
  },
  Review: {
    author: (parent, args, context) => {
      const authorId = parent.author_id;
      return _db.authors.find((author) => author.id === authorId);
    },
    game: (parent, args, context) => {
      const gameId = parent.game_id;
      return _db.games.find((game) => game.id === gameId);
    },
  },
  // Mutation
  Mutation: {
    deleteGame: (_, args) => {
      const gameId = args.id;
      _db.games = _db.games.filter((g) => g.id !== gameId);

      return _db.games;
    },
    addGame: (_, args) => {
      let game = {
        ...args.game,
        id: Math.floor(Math.random() * 10000).toString(),
      };
      _db.games.push(game);

      return game;
    },
    updateGame: (_, args) => {
      _db.games = _db.games.map((game) => {
        if (game.id === args.id) {
          return {
            ...game,
            ...args.updateGameDto,
          };
        }
        return game;
      });

      return _db.games.find((game) => game.id === args.id);
    },
  },
};

/*
* Apollo do the rest
games {
  title
}
*/

const app = express();
const httpServer = http.createServer(app);

// Set up Apollo Server
const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();

app.use(cors(), bodyParser.json(), expressMiddleware(server));

await new Promise((resolve) => httpServer.listen({ port: PORT }, resolve));
console.log(`🚀 Server is ready at http://localhost:${PORT}`);

_db.js

let games = [
  { id: "1", title: "Zelda, Tears of the Kingdom", platform: ["Switch"] },
  { id: "2", title: "Final Fantasy 7 Remake", platform: ["PS5", "Xbox"] },
  { id: "3", title: "Elden Ring", platform: ["PS5", "Xbox", "PC"] },
  { id: "4", title: "Mario Kart", platform: ["Switch"] },
  { id: "5", title: "Pokemon Scarlet", platform: ["PS5", "Xbox", "PC"] },
];

let authors = [
  { id: "1", name: "mario", verified: true },
  { id: "2", name: "yoshi", verified: false },
  { id: "3", name: "peach", verified: true },
];

let reviews = [
  { id: "1", rating: 9, content: "lorem ipsum", author_id: "1", game_id: "2" },
  { id: "2", rating: 10, content: "lorem ipsum", author_id: "2", game_id: "1" },
  { id: "3", rating: 7, content: "lorem ipsum", author_id: "3", game_id: "3" },
  { id: "4", rating: 5, content: "lorem ipsum", author_id: "2", game_id: "4" },
  { id: "5", rating: 8, content: "lorem ipsum", author_id: "2", game_id: "5" },
  { id: "6", rating: 7, content: "lorem ipsum", author_id: "1", game_id: "2" },
  { id: "7", rating: 10, content: "lorem ipsum", author_id: "3", game_id: "1" },
];

export default { games, authors, reviews };

Analyze Details

  1. Query
query Reviews {
  reviews {
    id
    rating
    content
  }
}

image

  1. Query with Variables

Directly

query SingleReviewQuery {
  review(id: "1") {
    rating
    content
    author {
      name
      verified
    }
    game {
      title
      platform
      reviews {
        rating
      }
    }
  }
}

image

With variables

query SingleReviewQuery($reviewId: ID!) {
  review(id: $reviewId) {
    rating
    content
    author {
      name
      verified
    }
    game {
      title
      platform
      reviews {
        rating
      }
    }
  }
}

image

  1. Mutation
mutation CreateGameMutation($game: AddGameInput!) {
  addGame(game: $game) {
    id
    title
  }
}
{
  "game": {
    "title": "Game 100",
    "platform": ["PC"]
  }
}

image

References

image

GraphQL: Syntax Highlighting