Fullstack with GraphQL, Prisma, Node, React and Apollo Boost
Tags: graphql, prisma, node, react
Published: 2020-08-05 04:14:09

การพัฒนา Web Application ปกติ เราจะใช้ Architecture แบบ 3-tier Architecture

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1w6lo1x1u74txv8k44do.png)

ซึ่ง API Server จะทำหน้าที่อยู่ 2 ส่วนหลักๆ คือ การทำ bussiness logic กับการติดต่อกับฐานข้อมูล ซึ่งการติดต่อกับฐานข้อมูลนั้นค่อนข้างวุ่นวาย มีทั้งการทำ ORM, Migration และเขียนคิวรี่ ซึ่งต้องเขียนให้ปลอดภัย และได้ performance ที่ดีอีก จึงมีแนวคิดที่จะแยกงานส่วนนี้ออกมาจก API Server เป็น Data Access Layer (DAL)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wsqnxevurhguro636xc0.png)

โดยตัว DAL เราจะใช้ Prisma มาจัดการ

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/634a80suns3ub4gr2anj.png)

ส่วน API Server จะติดต่อกับ Prisma Server ผ่านทาง Prisma client และตัว API Server ก็จะทำเป็น GraphQL Server โดย Architecture ทั้งหมดจะเป็นแบบนี้

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/bbxc5dtuyvhcm0ekjxxg.png)

# GraphQL Introduction

## What is GraphQL?

- New API standard developed by Facebook
- Specification for type system & query language
- Core primitives: Query, Mutation & Subscription

## GraphQL Vs Rest

- Single Endpoint/Multiple Endpoints
- Client decides how data return/Server decides how data return
- Schemaful/Schemaless

## How GraphQL Works

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dbj5r1v12l47bzndikjm.png)

## Understand GraphQL Servers

1. Definition: The GraphQL Schema
2. Implementation: Resolver Functions
3. Setup: Framework, Network (HTTP), Middlewares

### The GraphQL Schema

- Strongly typed & written in GraphQL Schema Definition Language (SDL)
- Defines API capabilities (contract for client-server communication)
- Used for: Auto-generated docs, codegen, automated tests, ...
- The Schema Definition Language (SDL)

```graphql
type Task {
  id: ID!
  text: String!
  completed: Boolean!
}

type Query {
  tasks(search: String): [Task!]!
  task(id: ID!): Task
}

type Mutation {
  createTask(text: String!): Task!
  toggleTask(id: ID!): Task
  deleteTask(id: ID!): Task
}
```

### Resolver Function

- Concrete implementation of the API
- One resolver function per field in SDL schema
- Query execution: Invoke resolvers for all fields in query

```jsx
fieldName:(root, args, context, info) => { result }
```

- Example CRUD for Task type

```jsx
const resolver = {
  Query: {
    tasks: (root, args, context, info) => context.db.getTasks(args.search),
    task: (root, args, context, info) => context.db.getTask(args.id)
  },
  Mutation: {
    createTask: (root, args, context, info) => context.db.createTask(args.text),
    toggleTask:(root, args, context, info) => context.db.toggleStatus(args.id),
    deleteTask: (root, args, context, info) => context.db.deleteTask(args.id)
  }
};
```

### Setup

- "GraphQL engine" to orchestrate resolver invocations
- Network layer based on "graphql-yoga" (network configuration: port, endpoints, CORS ... )
- Middleware (analytics, logging, crash reporting ... )

### Put it all together

```jsx
import { GraphQLServer } from 'graphql-yoga';

const typeDefs = `
  type Query {
    hello(name: String): String!
  }
`;

const resolvers = {
  Query: {
    hello: (_, { name }) => `Hello ${name || 'World'}`,
  },
};

const server = new GraphQLServer({ typeDefs, resolvers });
server.start(() => console.log('Server is running on localhost:4000'));
```

```jsx
import { GraphQLServer } from 'graphql-yoga';

const tasks = [
  {
    id: 'task-1',
    text: 'Learn GraphQL',
    completed: false,
  },
];

let nextTaskId = 2;

const typeDefs = `
  type Task {
    id: ID!
    text: String!
    completed: Boolean!
  }

  type Query {
    tasks(search: String): [Task!]!
    task(id: ID!): Task
  }

  type Mutation {
    createTask(text: String!): Task!
    toggleTask(id: ID!): Task
    deleteTask(id: ID!): Task
  }
`;

const resolvers = {
  Query: {
    tasks: (root, args, context, info) =>
      args.search
        ? tasks.filter(task => task.text.toLocaleLowerCase().includes(args.search.toLocaleLowerCase()))
        : tasks,
    task: (root, args, context, info) => tasks.find(task => task.id === args.id),
  },
  Mutation: {
    createTask: (root, args, context, info) => {
      const newTask = {
        // eslint-disable-next-line no-plusplus
        id: `task-${nextTaskId++}`,
        text: args.text,
        completed: false,
      };
      tasks.push(newTask);
      return newTask;
    },
    toggleTask: (root, args, context, info) => {
      const taskToUpdate = tasks.find(task => task.id === args.id);
      taskToUpdate.completed = !taskToUpdate.completed;
      return taskToUpdate;
    },
    deleteTask: (root, args, context, info) => {
      const deleteIndex = tasks.findIndex(task => task.id === args.id);
      if (deleteIndex > -1) {
        const deleted = tasks.splice(deleteIndex, 1);
        return deleted[0];
      }
      return null;
    },
  },
};

const server = new GraphQLServer({ typeDefs, resolvers });
server.start(() => console.log('Server is running on localhost:4000'));
```

### Refactor

- Use schema.graphql

```graphql
type Task {
  id: ID!
  text: String!
  completed: Boolean!
}

type Query {
  tasks(search: String): [Task!]!
  task(id: ID!): Task
}

type Mutation {
  createTask(text: String!): Task!
  toggleTask(id: ID!): Task
  deleteTask(id: ID!): Task
}
```

- Create resolver module

```jsx
// resolves/index.js
const tasks = [
  {
    id: 'task-1',
    text: 'Learn GraphQL',
    completed: false,
  },
];

let nextTaskId = 2;

const resolvers = {
  Query: {
    tasks: (root, args, context, info) =>
      args.search
        ? tasks.filter(task => task.text.toLocaleLowerCase().includes(args.search.toLocaleLowerCase()))
        : tasks,
    task: (root, args, context, info) => tasks.find(task => task.id === args.id),
  },
  Mutation: {
    createTask: (root, args, context, info) => {
      const newTask = {
        // eslint-disable-next-line no-plusplus
        id: `task-${nextTaskId++}`,
        text: args.text,
        completed: false,
      };
      tasks.push(newTask);
      return newTask;
    },
    toggleTask: (root, args, context, info) => {
      const taskToUpdate = tasks.find(task => task.id === args.id);
      taskToUpdate.completed = !taskToUpdate.completed;
      return taskToUpdate;
    },
    deleteTask: (root, args, context, info) => {
      const deleteIndex = tasks.findIndex(task => task.id === args.id);
      if (deleteIndex > -1) {
        const deleted = tasks.splice(deleteIndex, 1);
        return deleted[0];
      }
      return null;
    },
  },
};

export { resolvers };
```

- Update index.js

```jsx
import { resolvers } from './resolvers';

// ...

const server = new GraphQLServer({
  typeDefs: './src/schema.graphql',
  resolvers,
});

// ...
```

## Query & Mutation

- Query

```graphql
query {
  tasks {
    id
    text
    completed
  }
}
```

- Arguments

```graphql
query {
  tasks(search: "graphql") {
    id
    text
    completed
  }
}
```

- Mutation

```graphql
mutation {
  createTask(text: "Learn Prisma") {
    id
    text
  }
}
```

- Operation Name

```graphql
mutation createTask{
  createTask(text: "Learn Prisma") {
    id
    text
  }
}

query getAllTask{
  tasks(search: "") {
    id
    text
    completed
  }
}
```

- Fragment

```graphql
mutation createTask {
  createTask(text: "Learn React") {
    ...taskFields
  }
}

query getAllTask {
  tasks(search: "") {
    ...taskFields
  }
}

fragment taskFields on Task {
  id
  text
  completed
}
```

- Alias

```graphql
query getAllTask {
  allTasks: tasks(search: "") {
    ...taskFields
  }
}

fragment taskFields on Task {
  taskId: id
  text
  completed
}
```

- Variable

```graphql
query getAllTask($search: String) {
  allTasks: tasks(search: $search) {
    ...taskFields
  }
}
```

- Directive

```graphql
query getAllTask($search: String, $withStatus: Boolean!) {
  tasks(search: $search) {
    id
    text
    completed @include(if: $withStatus)
  }
}
```

# Prisma Introduction

## What is Prisma?

- DB-agnostic data access layer (think ORM)
- Prisma server & Prisma client
- Declarative data modelling & migrations

## 3-Main Concept

- Datamodel
- Prisma Server
- Prisma Client

### Datamodel

- Defines the models of your application and is foundation for the Prisma client API.
- The datamodel is written in .prisma-files and uses GraphQL SDL syntax.
- Using the datamodel for database migrations (optional)
- datamodel.prisma

```graphql
type Task {
  id: ID! @id
  text: String!
  completed: Boolean! @default(value: false)
  createdAt: DateTime! @createdAt
  updatedAt: DateTime! @updatedAt
}
```

### Prisma Server

- The Prisma server is a standalone infrastructure component that is connected to your database
- Prisma servers can be run with Docker
- docker-compose.yml

```docker
version: '3'
services:
  prisma:
    image: prismagraphql/prisma:1.34
    restart: always
    ports:
    - "4466:4466"
    environment:
      PRISMA_CONFIG: |
        port: 4466
        # uncomment the next line and provide the env var PRISMA_MANAGEMENT_API_SECRET=my-secret to activate cluster security
        # managementApiSecret: my-secret
        databases:
          default:
            connector: postgres
            host: postgres
            user: prisma
            password: prisma
            rawAccess: true
            port: 5432
            migrations: true
  postgres:
    image: postgres
    restart: always
    # Uncomment the next two lines to connect to your your database from outside the Docker environment, e.g. using a database GUI like Postico
    # ports:
    # - "5432:5432"
    environment:
      POSTGRES_USER: prisma
      POSTGRES_PASSWORD: prisma
    volumes:
      - postgres:/var/lib/postgresql/data
volumes:
  postgres:
```

- Prisma Services

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/h9ll69w9c43ndi8qkkpi.png)

- prisma.yml

```docker
endpoint: http://localhost:4466
datamodel: datamodel.prisma
```

### Prisma Client

- The Prisma client is an auto-generated library that replaces a traditional ORM in your API
- It connects to a Prisma server which sits on top your database

    ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gxd0sij4irf1zpeutzz1.png)

- update prisma.yml

```docker
endpoint: http://localhost:4466
datamodel: datamodel.prisma

generate:
  - generator: javascript-client
    output: ./generated/prisma-client/

hooks:
  post-deploy:
    - prisma generate
```

- demo

```jsx
// npm init -y && npm i prisma-client-lib
// playgroud.js
const {prisma} = require('./generated/prisma-client');

// A `main` function so that we can use async/await
async function main() {  
  // Create a new task
  const newTask = await prisma.createTask({ text: 'Learn React' });
  console.log(`Created new task: ${newTask.text} (ID: ${newTask.id})`);

  // Read all tasks from the database and print them to the console
  const allTasks = await prisma.tasks();
  console.log('All Tasks:', allTasks);

  // Fetch single task
  const task = await prisma.task({ id: allTasks[0].id });
  console.log('Task:', task);

  // Filter task list
  const filteredTask = await prisma.tasks({
    where: {
      text: task.text,
    },
  });
  console.log('Filtered: ', filteredTask);

  // Update task to completed
  const updatedUser = await prisma.updateTask({
    where: { id: task.id },
    data: { completed: true },
  });
  console.log('Updated:', updatedUser);

  // Delete task
  const deletedTask = await prisma.deleteTask({ id: task.id });
  console.log('Deleted:', deletedTask);
}

main().catch(e => console.error(e));
```

## How Prisma Works

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/y0nbjjlmgq34x5kvifus.png)

## Add data access layer to GraphQL Server with Prisma

- Step 1: update prisma.yml

```yaml
generate:
  - generator: javascript-client
    output: ../gql-server/src/generated/prisma-client/
```

- Step 2: Run `prisma generate`
- Step 3: Run `npm i prisma-client-lib`
- Step 4: Add database layer with Prisma

```jsx
import { prisma } from './generated/prisma-client';

//...

const server = new GraphQLServer({
  typeDefs,
  resolvers,
  context: {
    prisma,
  },
});
```

- Step 5: Complete API operations against the database
  
  - Create `resolvers/Query.js`

  ```jsx
  const Query = {
    tasks: (parent, { search }, { prisma }, info) =>
      prisma.tasks({
        where: {
          text_contains: search,
        },
      }),
    task: (parent, { id }, { prisma }, info) => prisma.task({ id }),
  };

  export { Query };
  ```

  - Create `resolves/Mutation.js`

  ```jsx
  const Mutation = {
    createTask: (root, { text }, { prisma }, info) => prisma.createTask({ text }),
    toggleTask: async (root, { id }, { prisma }, info) => {
      const taskToUpdate = await prisma.task({ id });
      return prisma.updateTask({
        where: { id },
        data: {
          completed: !taskToUpdate.completed,
        },
      });
    },
    deleteTask: (root, { id }, { prisma }, info) => prisma.deleteTask({ id }),
  };

  export { Mutation };
  ```

  - Update `resolves/index.js`

  ```jsx
  import { Query } from './Query';
  import { Mutation } from './Mutation';

  const resolvers = {
    Query,
    Mutation,
  };

  export { resolvers };
  ```

# Create Frontend App with React and Apollo Boost

## Create React App

- Use `create-react-app` run `npx create-react-app client`

### React Basic

1. [JSX](https://github.com/somprasongd/todo-react-app/tree/1-jsx)
  
- JSX & CSS

```jsx
import React from 'react';
import './App.css';

function App() {
  const headerStyle = { color: 'green', fontSize: '50px' };
  return (
    <div>
      <h1 style={headerStyle}>Todo App ({4})</h1>
      <ul>
        <li className="text">Learn React</li>
        <li className="text">Learn React</li>
        <li className="text">Learn React</li>
        <li className="text">Learn React</li>
      </ul>
    </div>
  );
}

export default App;
```

```css
.text {
  color: Tomato;
}
```

- Loop

```jsx
import React from 'react';
import './App.css';

const todos = [
  {
    id: 1,
    text: 'Learn React'
  },
  {
    id: 2,
    text: 'Learn GraphQL'
  },
  {
    id: 3,
    text: 'Learn Prisma'
  }
];

function App() {
  const headerStyle = { color: 'green', fontSize: '50px' };
  return (
    <div>
      <h1 style={headerStyle}>Todo App ({todos.length})</h1>
      <ul>
        {todos.map(todo => (
          <li className="text" key={todo.id}>
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;
```

- Condition

```jsx
import React from 'react';
import './App.css';

const todos = [
  {
    id: 1,
    text: 'Learn React'
  },
  {
    id: 2,
    text: 'Learn GraphQL'
  },
  {
    id: 3,
    text: 'Learn Prisma'
  }
];

// if-else or Ternary Operator or Logical && Operator

function App() {
  const headerStyle = { color: 'green', fontSize: '50px' };
  const count = todos.length;
  return (
    <div>
      <h1 style={headerStyle}>Todo App ({todos.length})</h1>
      {count === 0 ? (
        <h3>All Done...</h3>
      ) : (
        <ul>
          {todos.map(todo => (
            <li className="text" key={todo.id}>
              {todo.text}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default App;
```

- Use Semantic UI
    - Add Semantic UI React
        - Components: `npm i semantic-ui-react`
        - Theme: `npm i semantic-ui-css` and import in index.js `import 'semantic-ui-css/semantic.min.css'`

        ```jsx
        import React from 'react';
        import { Container, Segment } from 'semantic-ui-react';
        import './App.css';

        const todos = [
          {
            id: 1,
            text: 'Learn React'
          },
          {
            id: 2,
            text: 'Learn GraphQL'
          },
          {
            id: 3,
            text: 'Learn Prisma'
          }
        ];

        function App() {
          const headerStyle = { color: 'green', fontSize: '50px' };
          const count = todos.length;
          return (
            <Container>
              <h1 style={headerStyle}>Todo App ({todos.length})</h1>
              {count === 0 ? (
                <h3>All Done...</h3>
              ) : (
                <>
                  {todos.map(todo => (
                    <Segment raised key={todo.id}>
                      {todo.text}
                    </Segment>
                  ))}
                </>
              )}
            </Container>
          );
        }

        export default App;
        ```

2. [Components & Props](https://github.com/somprasongd/todo-react-app/tree/3-components)

- `TodoTitle.jsx`

```jsx
import React from 'react';
import { Icon, Header } from 'semantic-ui-react';

function TodoTitle(props) {
  const count = props.count;
  return (
    <Header as="h2" color="teal" textAlign="left">
      <Icon name="tasks" />
      Todo App ({count})
    </Header>
  );
}

export default TodoTitle;
```

- `TodoItem.jsx`

```jsx
import React from 'react';
import { Segment } from 'semantic-ui-react';

function TodoItem(props) {
  const { text } = props;
  return <Segment raised>{text}</Segment>;
}

export default TodoItem;
```

- `Todo.jsx`

```jsx
import React from 'react';
import { Message, Loader, Grid } from 'semantic-ui-react';

import TodoTitle from './TodoTitle';
import TodoItem from './TodoItem';

const todos = [
  {
    id: 1,
    text: 'Learn React'
  },
  {
    id: 2,
    text: 'Learn GraphQL'
  },
  {
    id: 3,
    text: 'Learn Prisma'
  }
];

function Todo() {
  const count = todos.length;
  return (
    <Grid
      textAlign="center"
      style={{ height: '100vh', marginTop: '25px' }}
      verticalAlign="top"
    >
      <Grid.Column style={{ maxWidth: 600 }}>
        <TodoTitle count={count} />
        {loading && <Loader active inline="centered" />}
        {!loading && count === 0 ? (
          <Message success compact header="Hooray🎉 🎉 🎉 All Done 😆" />
        ) : (
          <>
            {todos.map(todo => (
              <TodoItem key={todo.id} todo={todo} onDelete={handleRemoveTodo} />
            ))}
          </>
        )}
      </Grid.Column>
    </Grid>
  );
}

export default Todo;
```

3. [State & Handling Events](https://github.com/somprasongd/todo-react-app/tree/2-events)

- Counter App

  - Class Component

  ```jsx
  import React from 'react';

  class Counter extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        count: 0
      };
    }
    increment = () => {
      this.setState({
        count: (this.state.count += 1)
      });
    };
    decrement = () => {
      this.setState({
        count: (this.state.count -= 1)
      });
    };
    render() {
      return (
        <>
          <h2>{this.state.count}</h2>
          <div>
            <button onClick={this.increment}>+</button>
            <button onClick={this.decrement}>-</button>
          </div>
        </>
      );
    }
  }

  export default Counter;
  ```

  - Hook

  ```jsx
  import React, { useState } from 'react';

  function Counter() {
    const [count, setCount] = useState(0);

    const increment = () => {
      setCount(count + 1);
    };
    const decrement = () => {
      setCount(count - 1);
    };

    return (
      <>
        <h2>Count with Hook: {count}</h2>
        <div>
          <button onClick={increment}>+</button>
          <button onClick={decrement}>-</button>
        </div>
      </>
    );
  }

  export default Counter;
  ```

- Todo App
  - Add

  ```jsx
  import React, { useState } from 'react';
  import TodoTitle from './TodoTitle';
  import TodoItem from './TodoItem';
  import { Button } from 'semantic-ui-react';

  function Todo() {
    const [todos, setTodos] = useState([]);

    const handleAddTodo = () => {
      const newTodo = {
        id: todos.length + 1,
        text: 'Test Add Todo'
      };
      setTodos([...todos, newTodo]);
    };

    const count = todos.length;
    return (
      <>
        <TodoTitle count={count} />
        <Button primary onClick={handleAddTodo}>
          Add
        </Button>
        {count === 0 ? (
          <h3>All Done...</h3>
        ) : (
          <>
            {todos.map(todo => (
              <TodoItem key={todo.id} text={todo.text} />
            ))}
          </>
        )}
      </>
    );
  }

  export default Todo;
  ```

  - Delete
    - Todo.jsx

    ```jsx
    import React, { useState, useRef } from 'react';
    import { Message, Loader, Grid } from 'semantic-ui-react';
    import TodoTitle from './TodoTitle';
    import TodoItem from './TodoItem';

    function Todo() {
      const [todos, setTodos] = useState([]);
      const nextId = useRef(1);

      const handleAddTodo = () => {
        const id = nextId.current++;
        const newTodo = {
          id,
          text: 'Test Add Todo: ' + id
        };
        setTodos([...todos, newTodo]);
      };

      const handleRemoveTodo = id => {
        console.log('Remove id:', id);
        // Filter all todos except the one to be removed
        const remainder = todos.filter(todo => todo.id !== id);
        // Update state with filter
        setTodos(remainder);
      };

      const count = todos.length;
      return (
        <Grid
          textAlign="center"
          style={{ height: '100vh', marginTop: '25px' }}
          verticalAlign="top"
        >
          <Grid.Column style={{ maxWidth: 600 }}>
            <TodoTitle count={count} />
            <Button primary onClick={handleAddTodo}>
              Add
            </Button>
            {loading && <Loader active inline="centered" />}
            {!loading && count === 0 ? (
              <Message success compact header="Hooray🎉 🎉 🎉 All Done 😆" />
            ) : (
              <>
                {todos.map(todo => (
                  <TodoItem key={todo.id} todo={todo} onDelete={handleRemoveTodo} />
                ))}
              </>
            )}
          </Grid.Column>
        </Grid>
      );
    }

    export default Todo;
    ```

    - TodoItem.jsx

    ```jsx
    import React from 'react';
    import { Segment, Button } from 'semantic-ui-react';

    function TodoItem(props) {
      const {
        todo: { id, text, completed = false },
        onDelete
      } = props;

      return (
        <>
          <Segment textAlign="left">
            {text}
            <Button
              floated="right"
              style={{ marginTop: '-8px' }}
              icon="delete"
              color="red"
              onClick={() => {
                onDelete(id);
              }}
            />
          </Segment>
        </>
      );
    }

    export default TodoItem;
    ```

4. Form

```jsx
import React, { useState } from 'react';
import { Form, Message } from 'semantic-ui-react';

function TodoForm({ onSubmit }) {
  const [text, setText] = useState('');
  const [isWarning, setIsWarning] = useState(false);
  const handleChange = e => {
    setIsWarning(false);
    setText(e.target.value);
  };

  const handleSubmit = e => {
    e.preventDefault();
    if (text === '') {
      return setIsWarning(true);
    }
    onSubmit(text);
    setText('');
  };

  return (
    <Form warning onSubmit={handleSubmit}>
      <Form.Field>
        <Form.Input
          placeholder="What needs to be done?"
          name="text"
          value={text}
          onChange={handleChange}
        />
      </Form.Field>
      {isWarning && <Message warning header="Please input something!" />}
    </Form>
  );
}

export default TodoForm;
```

5. [Lifecycle](https://github.com/somprasongd/todo-react-app/tree/4-lifecycle)

    ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8j05nwr2n9nfa2asbo0f.png)

  - Class Component

  ```jsx
  import React, { Component } from 'react';

  class Clock extends Component {
    state = { date: new Date() };

    componentDidMount() {
      console.log('componentDidMount');

      this.timerID = setInterval(() => this.tick(), 1000);
    }
    componentDidUpdate(prevProps, prevState) {
      console.log('componentDidUpdate');
    }

    componentWillUnmount() {
      console.log('componentWillUnmount');
      clearInterval(this.timerID);
    }

    tick() {
      console.log('tick');

      this.setState({
        date: new Date()
      });
    }

    render() {
      console.log('render');
      return (
        <div>
          <h2>Time is {this.state.date.toLocaleTimeString()}.</h2>
        </div>
      );
    }
  }

  export default Clock;
  ```

  - Hook

  ```jsx
  import React, { useState, useEffect } from 'react';

  function Clock() {
    const [date, setDate] = useState(new Date());

    useEffect(() => {
      const timerID = setInterval(() => tick(), 1000);
      return () => {
        clearInterval(timerID);
      };
    }, []);

    const tick = () => {
      setDate(new Date());
    };

    return (
      <div>
        <h2>Time (Hook) is {date.toLocaleTimeString()}.</h2>
      </div>
    );
  }

  export default Clock;
  ```

6. [HTTP Request](https://github.com/somprasongd/todo-react-app/tree/5-http-requests)

- `npm install axios`
- Edit Todo.jsx

```jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Message, Loader, Grid } from 'semantic-ui-react';
import config from '../config';
import TodoTitle from './TodoTitle';
import TodoItem from './TodoItem';
import TodoForm from './TodoForm';

function Todo() {
  const [todos, setTodos] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const loadData = async () => {
      // Make HTTP reques with Axios
      axios.get(config.API_URL).then(res => {
        // Set state with result
        setTodos(res.data);
        setLoading(false);
      });
    };
    loadData();
  }, []);

  const handleAddTodo = text => {
    // Assemble data
    const newTodo = { text, isCompleted: false };
    // Update data
    axios.post(config.API_URL, newTodo).then(res => {
      setTodos([...todos, res.data]);
    });
  };

  const handleRemoveTodo = id => {
    // Filter all todos except the one to be removed
    const remainder = todos.filter(todo => todo.id !== id);
    // Update state with filter
    axios.delete(`${config.API_URL}/${id}`).then(res => {
      setTodos(remainder);
    });
  };

  const count = todos.length;
  return (
    <Grid
      textAlign="center"
      style={{ height: '100vh', marginTop: '25px' }}
      verticalAlign="top"
    >
      <Grid.Column style={{ maxWidth: 600 }}>
        <TodoTitle count={count} />
        <TodoForm onSubmit={handleAddTodo} />
        {loading && <Loader active inline="centered" />}
        {!loading && count === 0 ? (
          <Message success compact header="Hooray🎉 🎉 🎉 All Done 😆" />
        ) : (
          <>
            {todos.map(todo => (
              <TodoItem key={todo.id} todo={todo} onDelete={handleRemoveTodo} />
            ))}
          </>
        )}
      </Grid.Column>
    </Grid>
  );
}

export default Todo;
```

## Connect your API to a client

### How React connect to GraphQL Server

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/w63uxibykvxyd1a2iuxr.png)

### Set up Apollo Client

- Install packages `npm install apollo-boost @apollo/react-hooks graphql`

> `apollo-boost`: Package containing everything you need to set up Apollo Client
> `@apollo/react-hooks`: React hooks based view layer integration
> `graphql`: Also parses your GraphQL queries

- Create Apollo Client in `index.js`

```jsx
import ApolloClient from 'apollo-boost';

const client = new ApolloClient({
  uri: 'http://localhost:4000',
});
```

- Make your first query

```jsx
import { gql } from 'apollo-boost';
// or you can use `import gql from 'graphql-tag';` instead

// ...

client
  .query({
    query: gql`
      query fetchAllTask {
        tasks {
          id
          text
          completed
        }
      }
    `
  })
  .then(result => console.log(result));
```

- Connect your client to React

```jsx
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from '@apollo/react-hooks';

const client = new ApolloClient({
  uri: 'http://localhost:4000',
});

ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>
  , document.getElementById('root'));
```

### Query

- Request data in Component
  - Create `gql/index.js`

  ```jsx
  import { gql } from 'apollo-boost';

  export default {
    Query: {
      getAllTodos: gql`
        query fetchAllTasks {
          todos: tasks {
            id
            text
            completed
          }
        }
      `
    }
  };
  ```

  - Create `TodoList.jsx`

  ```jsx
  import React from 'react';
  import { Loader, Message } from 'semantic-ui-react';
  import { useQuery } from '@apollo/react-hooks';
  import gql from '../gql';
  import ErrorMessage from './ErrorMessage';
  import TodoItem from './TodoItem';

  function TodoList() {
    const { loading, error, data } = useQuery(gql.Query.getAllTodos);
    if (loading) return <Loader active inline="centered" />;
    if (error) return <ErrorMessage message={error.message} />;

    const { todos } = data;
    const count = todos.length;

    const component =
      count === 0 ? (
        <Message success compact header="Hooray🎉 🎉 🎉 All Done 😆" />
      ) : (
        <>
          {todos.map(todo => (
            <TodoItem key={todo.id} todo={todo} />
          ))}
        </>
      );
    return component;
  }

  export default TodoList;
  ```

  - Update `TodoTitle.jsx`

  ```jsx
  import React from 'react';
  import { Icon, Header } from 'semantic-ui-react';
  import { useQuery } from '@apollo/react-hooks';
  import gql from '../gql';

  function TodoTitle() {
    const { loading, data } = useQuery(gql.Query.getAllTodos);
    let count = 0;
    if (!loading) count = data.todos.length;
    return (
      <Header as="h2" color="teal" textAlign="left">
        <Icon name="tasks" />
        Todo App ({count})
      </Header>
    );
  }

  export default TodoTitle;
  ```

### Mutation

- Executing a mutation
  - Update `gql/index.js`

  ```jsx
  import { gql } from 'apollo-boost';

  export default {
    Query: {
      getAllTodos: gql`
        query fetchAllTasks {
          todos: tasks {
            id
            text
            completed
          }
        }
      `
    },
    Mutation: {
      addTodo: gql`
        mutation addTask($text: String!) {
          addTodo: createTask(text: $text) {
            id
            text
            completed
          }
        }
      `
    }
  };
  ```

  - Update `TodoForm.jsx`

  ```jsx
  import React, { useState } from 'react';
  import { Form, Message } from 'semantic-ui-react';
  import gql from '../gql';
  import { useMutation } from '@apollo/react-hooks';

  function TodoForm() {
    const [text, setText] = useState('');
    const [isWarning, setIsWarning] = useState(false);

    const [addTodo, { data }] = useMutation(gql.Mutation.addTodo);

    const handleChange = e => {
      setIsWarning(false);
      setText(e.target.value);
    };

    const handleSubmit = e => {
      e.preventDefault();
      if (text === '') {
        return setIsWarning(true);
      }
      addTodo({ variables: { text } });
      setText('');
    };

    return (
      <Form warning onSubmit={handleSubmit}>
        <Form.Field>
          <Form.Input
            placeholder="What needs to be done?"
            name="text"
            value={text}
            onChange={handleChange}
          />
        </Form.Field>
        {isWarning && <Message warning header="Please input something!" />}
      </Form>
    );
  }

  export default TodoForm;
  ```

  - Update `TodoItem.jsx`

  ```jsx
  import React from 'react';
  import { Segment, Button, Icon } from 'semantic-ui-react';
  import { useMutation } from '@apollo/react-hooks';
  import gql from '../gql';

  function TodoItem(props) {
    const {
      todo: { id, text, completed = false }
    } = props;

    const [deleteTodo, { data }] = useMutation(gql.Mutation.deleteTodo);

    const [toggleTodo] = useMutation(gql.Mutation.toggleTodo);

    return (
      <>
        <Segment textAlign="left">
          <Icon
            color={completed ? 'teal' : 'grey'}
            name="check circle"
            size="large"
            onClick={() => {
              toggleTodo({
                variables: {
                  id
                }
              });
            }}
          />
          {text}
          <Button
            floated="right"
            style={{ marginTop: '-8px' }}
            icon="delete"
            color="red"
            onClick={() => {
              deleteTodo({
                variables: {
                  id
                }
              });
            }}
          />
        </Segment>
      </>
    );
  }

  export default TodoItem;
  ```

- Updating the cache after a mutation

> ถ้าการอัพเดท แล้ว response id กลับมาด้วย จะทำการอัพเดท cache ให้อัตโนมัติ เพราะ cache อ้างอิงจาก id 
> ส่วนการ create กับ delete ต้องไปอัพเดท cache เอง แล้วมันจะ render ใหม่
  
  - Update `TodoForm.jsx`

  ```jsx
  const [addTodo] = useMutation(gql.Mutation.addTodo, {
      update(cache, { data: { addTodo } }) {
        const { todos } = cache.readQuery({ query: gql.Query.getAllTodos });
        cache.writeQuery({
          query: gql.Query.getAllTodos,
          data: { todos: [...todos, addTodo] }
        });
      }
    });
  ```

  - Update `TodoItem.jsx`

  ```jsx
  const [deleteTodo] = useMutation(gql.Mutation.deleteTodo, {
      update(cache, { data: { deleteTodo } }) {
        const { todos } = cache.readQuery({ query: gql.Query.getAllTodos });
        const remainder = todos.filter(todo => todo.id !== deleteTodo.id);
        cache.writeQuery({
          query: gql.Query.getAllTodos,
          data: { todos: remainder }
        });
      }
    });
  ```