Create a project

nest new app-server # Create nest service
create-react-app app Create the React project

# app-server port=4000
yarn dev:start

# app port=3000
yarn start
Copy the code

The React to the client

Using the React Client The Apollo React Client is recommended.

yarn  add apollo-boost @apollo/react-hooks graphql
Copy the code
  • Apollo-boost contains everything you need to install the Apollo Client
  • @apollo/react-hooks Contains the react hooks function
  • Graphql is used for queries

NestJS server

Projects created using scaffolding also require GrapQL support

yarn add @nestjs/graphql graphql-tools graphql apollo-server-express
Copy the code

Because GraphQL is a separate module, NestJS supports it.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(400);
  console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();

Copy the code

The NestJS server is written in two ways: code first, architecture first, and we use code first. We need to add GraphQLModule in @nestjs to configure GraphQL

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { RecipesModule } from './recipes/recipes.module';

@Module({
  imports: [
    RecipesModule,
    GraphQLModule.forRoot({
      installSubscriptionHandlers: true.autoSchemaFile: 'schema.gql',})]})export class AppModule {}
Copy the code
  • The parameters of the forRoot method are passed to Apollo.

  • AutoSchemaFile is the GraphQL schema file automatically generated after running

Here’s what the RecipesModule’s GraphQL is about to solve.

// RecipesModule.ts
import { Module } from '@nestjs/common';
import { DateScalar } from '.. /common/scalars/date.scalar';
import { RecipesResolver } from './recipes.resolver';
import { RecipesService } from './recipes.service';

@Module({
  providers: [RecipesResolver, RecipesService, DateScalar],
})
export class RecipesModule {}
Copy the code

This value implements a function called Query recipe

import { NotFoundException } from '@nestjs/common';
import { Args, Mutation, Query, Resolver, Subscription } from '@nestjs/graphql';
import { Recipe } from './models/recipe';
import { RecipesService } from './recipes.service';

@Resolver(of= > Recipe)
export class RecipesResolver {
  constructor(private readonly recipesService: RecipesService) {}

  @Query(returns= > Recipe)
  async recipe(@Args('id') id: string): Promise<Recipe> {
    const recipe = await this.recipesService.findOneById(id);
    if(! recipe) {throw new NotFoundException(id);
    }
    returnrecipe; }}Copy the code

Here are the service layers that RecipesResolver needs

// /recipes/recipes.module.ts

import { Injectable } from '@nestjs/common';
import { NewRecipeInput } from './dto/new-recipe.input';
import { Recipe } from './models/recipe';

@Injectable(a)export class RecipesService {
  // Here we Mock our own data
  // Note that the simulated event must be an event object
  async findOneById(id: string) :Promise<Recipe> {
    return {
      id: id,
      title: 'sdf',
      description: 'fsd',
      creationDate: new Date(),
      ingredients: ['sdfd']}as any; }}Copy the code

TypeScript defines GraphQL code

It relies on the Type-GraphQL package

// /recipes/models/recipe.ts

import { Field, ID, ObjectType } from 'type-graphql';

@ObjectType()
export class Recipe {
  @Field(type= > ID)
  id: string;

  @Field()
  title: string;

  @Field({ nullable: true}) description? : string; @Field() creationDate:Date;

  @Field(type= > [String])
  ingredients: string[];
}
Copy the code

Its corresponding Schema looks like this

type Recipe {
  id: ID!
  title: String!
  description: String
  creationDate: Date!
  ingredients: [String! ] ! }Copy the code

By comparison, we can see the function of the decorator provided by ‘type-graphQL’

  • ObjectType decorates a class to indicate that it is a type
  • Field decorates the Field in GraphQL to indicate that it is a type
  • Type => ID Indicates that the returned value is the ID type, which corresponds to the String type
  • When the Field parameter is null, it indicates that it can be non-null
  • @field ({nullable: true}) indicates optional
  • @field (type => [String]) represents a non-empty array of strings, and the String itself cannot be empty.

Start nest service

yarn run dev:start
Copy the code

We can then query data on the localhost:4000/ GraphQL port, open it in a browser, and get a GraphQL playground.

Our playground left input, query object

{
  recipe(id: "23") {
    id
    title
    description,
    ingredients,
    creationDate
  }
}
Copy the code

We get the following result, which is the same as our custom data in Recipe. This means that GraphQL is already connected in the browser.

{
  "data": {
    "recipe": {
      "id": "23"."title": "sdf"."description": "fsd"."ingredients": [
        "sdfd"]."creationDate": 1591171809755}}}Copy the code

The React Client uses the Apollo Client

Let’s first query the data:

import ApolloClient, { gql } from 'apollo-boost';

const client = new ApolloClient({
  uri: 'http://localhost:3000/graphql'}); client .query({query: gql` { recipe(id: "23") { id title description, ingredients, creationDate } } `,
  })
  .then((result) = > console.log(result));
Copy the code

Result is a returned object with the following data structure:

const result ={
  data: {recipe: {
    creationDate: 1591171953828.description: "fsd".id: "23".ingredients: ["sdfd"].title: "sdf".__typename: "Recipe"
  }},
  loading: false.networkStatus: 7.stale: false
}
Copy the code

Here’s a simple example, but let’s use an API that is more in line with React development conventions.

  • Client functions as a top-level component that is passed to internal components, similar to the React-Redux Provider, which provides a context.
import { ApolloProvider } from '@apollo/react-hooks';
import ApolloClient, { gql } from 'apollo-boost';

const client = new ApolloClient({
  uri: 'http://localhost:3000/graphql'});function App() {
  return (
    <ApolloProvider client={client}></ApolloProvider>)}Copy the code
  • Use the query useQuery in the component
import React from 'react';
import { useQuery } from '@apollo/react-hooks';
import { gql } from 'apollo-boost';

const EXCHANGE_RATES = gql` { rates(currency: "USD") { currency rate } } `;

function ExchangeRates() {
  const { loading, error, data } = useQuery(EXCHANGE_RATES);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error :(</p>;

  return data.rates.map(({ currency, rate }) = > (
    <div key={currency}>
      <p>
        {currency}: {rate}
      </p>
    </div>
  ));
}
Copy the code
  • Use compilation in components
import gql from 'graphql-tag';
import { useMutation } from '@apollo/react-hooks';

const ADD_TODO = gql` mutation AddTodo($type: String!) { addTodo(type: $type) { id type } } `;

function AddTodo() {
  let input;
  const [addTodo, { data }] = useMutation(ADD_TODO);

  return (
    <div>
      <form
        onSubmit={e= > {
          e.preventDefault();
          addTodo({ variables: { type: input.value } });
          input.value = '';
        }}
      >
        <input
          ref={node= >{ input = node; }} / ><button type="submit">Add Todo</button>
      </form>
    </div>
  );
}
Copy the code

Local State Management

There are two main ways to perform local state mutations.

  • The first method is to write directly to the cache cache.writeData via a call. Direct writes are useful for one-time mutations that do not depend on the current data in the cache (for example, writing a single value).
  • The second approach is to use useMutation hooks with GraphQL mutations that call the local client parser. If your mutation depends on an existing value in the cache, such as adding an item to a list or switching booleans, we recommend using a parser.

Write directly

import React from "react";
import { useApolloClient } from "@apollo/react-hooks";

import Link from "./Link";

function FilterLink({ filter, children }) {
  const client = useApolloClient();
  return (
    <Link
      onClick={()= > client.writeData({ data: { visibilityFilter: filter } })}
    >
      {children}
    </Link>
  );
}
Copy the code

Local resolution

import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';

const client = new ApolloClient({
  cache: new InMemoryCache(),
  resolvers: {
    Mutation: {
      toggleTodo: (_root, variables, { cache, getCacheKey }) = > {
        const id = getCacheKey({ __typename: 'TodoItem'.id: variables.id })
        const fragment = gql` fragment completeTodo on TodoItem { completed } `;
        const todo = cache.readFragment({ fragment, id });
        constdata = { ... todo,completed: !todo.completed };
        cache.writeData({ id, data });
        return null; }},}});Copy the code
import React from "react"
import { useMutation } from "@apollo/react-hooks";
import gql from "graphql-tag";

const TOGGLE_TODO = gql` mutation ToggleTodo($id: Int!) { toggleTodo(id: $id) @client } `;

function Todo({ id, completed, text }) {
  const [toggleTodo] = useMutation(TOGGLE_TODO, { variables: { id } });
  return (
    <li
      onClick={toggleTodo}
      style={{
        textDecoration: completed ? "line-through" : "none,}} ">
      {text}
    </li>
  );
}
Copy the code

To subscribe to

In addition to fetching data using queries and modifying data using mutations, the GraphQL specification supports a third type of operation called subscription.

GraphQL subscriptions are a way to push data from the server to clients that choose to listen for real-time messages from the server. A subscription is similar to a query in that it specifies a set of fields to be passed to the client, but instead of immediately returning a single answer, the result is sent each time a specific event occurs on the server.

A common use case for subscriptions is to notify clients of specific events, such as creating new objects, updating fields, and so on.

type Subscription {
  commentAdded(repoFullName: String!). : Comment }Copy the code

paging

There are basically two ways to get paging data: numbered pages and cursors. There are also two ways to display paged data: discrete pages and infinite scrolling. For a deeper understanding of the differences between the two and when to use one versus the other, I suggest you read our blog post on Understanding pagination

fragment

fragment NameParts on Person {
  firstName
  lastName
}

query GetPerson {
  people(id: "Seven") {
    .NameParts
    avatar(size: LARGE)}}Copy the code

The following

Next we need to learn apollo-React and NestJS GraphQL in depth