# Implementation (NodeJS + React)

## Server-Side (NodeJS Express)

### Pre-Action&#x20;

```
npm install apollo-server-express
```

### &#x20;Structure

#### Data

* Define the data source

```javascript
const studentList =  [
    { sid: 1, name: "Peter", age: 18, courseId: [1,3]},
    { sid: 2, name: "Mary", age: 23, courseId: [1,2]}
];

const courseList = [
    { cid: 1, name: "Chinese", studentId:[1,2] },
    { cid: 2, name: "English", studentId: [2] },
    { cid: 3, name: "Math", studentId:[1]}
]

export {studentList, courseList};
```

#### Schema

* Define what type of input can be entered, the format of query, type, ......
* Separate by domain (e.g: Student and Course)

```javascript
// student.js
import { gql } from "apollo-server-express";

const schema = gql`
  input PostStudent {
    name:String!
    age:Int!
  }

  type Student{
      sid: Int!
      name: String!
      age: Int!
      courses: [Course]
  }
  
  extend type Query {
    students:[Student]
    student(id: Int!): Student!
  }

  extend type Mutation{
    submitStudent(input: PostStudent!): Student
    deleteStudent: Int
  }
`;

export default schema;
```

```javascript
//course.js
import { gql } from "apollo-server-express";

const schema = gql`
  type Course{
    cid: Int!
    name: String!
    students: [Student!]
  }

  extend type Query {
    courses:[Course]
    course(id: Int!): Course!
  }

`;

export default schema;
```

* Then group them together

```javascript
import { gql } from 'apollo-server-express';
 
import studentSchema from './student.js';
import courseSchema from './course.js';
 
const linkSchema = gql`
  type Query {
    _: Boolean
  }
 
  type Mutation {
    _: Boolean
  }
 
  type Subscription {
    _: Boolean
  }
`;
 
export default [linkSchema, studentSchema, courseSchema];
```

#### Resolver

* Define the implementation of the query, mutation, ......
* Separate by domain (e.g: Student and Course)

```javascript
// student.js
class Student {
    constructor(id,{name, age, courseId}){
      this.sid = id;
      this.name = name;
      this.age = age;
      this.courseId = courseId;
    }
}

const resolvers = {
    Query:{
      students: (parent, arg, {studentList}) => studentList.map((student, index) => new Student(index,student)),
      student: (parent, {id}, {studentList}) => new Student(id, studentList[id]),
      test: async(parent, arg, {dataSources}) => { 
        const data = await dataSources.StudentAPI.getStudentList();
        console.log(data);
        return data;
      }
    },
    
    Mutation:{
      submitStudent: (parent, {input}, {studentList}) =>{
        console.log('dataSources'. dataSources);
        id = studentList.length;
        studentList.push(input);
        return new Student(id, input);
      },
      deleteStudent: () => {
         id = studentList.length - 1;
         studentList.pop(studentList[id]);
         return id;
      }
    },
    
    Student:{
        courses: (parent, args, {courseList}) =>{
            const { courseId } = parent;
            if(courseId){
                return courseList.filter(course => courseId.includes(course.cid));
            }
            else{
                return [];
            }
        }
    }
};

export default resolvers;
```

```javascript
// course.js
class Course {
    constructor(id, {name, studentId}){
      this.cid = cid;
      this.name = name;
      this.studentId = studentId;
    }
}

const resolvers = {
    Query:{
      courses: (parent, arg, {courseList}) => courseList.map((course, index) => new Course(index, course)),
      course: (parent, {id}, {courseList}) => new Student(id, courseList[id])
    },

    Course:{
        students: (parent, args, {studentList}) =>{
            const { studentId } = parent;
            if(studentId){
                return studentList.filter(student => studentId.includes(student.sid));
            }
            else{
                return [];
            }
        }
    }
};

export default resolvers;
```

* Then put them together

```javascript
import studentResolvers from './student.js';
import courseResolvers from './course.js';
 
export default [studentResolvers, courseResolvers];
```

#### Output

* Group data, schema and resolver together&#x20;

```javascript
import express from "express";
const app = express();
import cors from "cors";
import bodyParser from "body-parser";
import  { ApolloServer } from "apollo-server-express";
import schema from "./graphql/schema/index.js";
import resolvers from "./graphql/resolver/index.js";
import {studentList, courseList} from "./graphql/data.js";

app.use(
    cors({
      origin: "*",
      methods: ["GET", "POST"],
      allowedHeaders: "Content-Type, Authorization, X-Requested-With",
    })
);

app.use(bodyParser.json());

const server = new ApolloServer({
  typeDefs: schema,
  resolvers,
  context: {
    studentList,
    courseList
  }
});

server.applyMiddleware({app, path:'/graphql'});

app.listen(8080, function () { 
  console.log("Server Start "); 
});
```

### Result

![](/files/-MME8uqv1a0xnrXkaKA0)

## Client-Side (React)

### Pre-Action

```bash
npm install @apollo/client
```

### Configuration

```javascript
import {ApolloClient, InMemoryCache} from '@apollo/client';

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

export default client;
```

```javascript
import { ApolloProvider } from '@apollo/client';
import client from "./config/apollo";

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

### Hook

* useQuery (To get the ideal format of data from API End Point)

```javascript
import React,{useEffect} from "react";
import { gql, useQuery } from "@apollo/client";
import { Table } from 'reactstrap';

//Define the query
const getUsersQuery = gql`
    query getStudents($id: Int!) {
     studentList: students{
        ...studentData
     }
     singleStudent: student(id: $id){
       ...studentData
     }
    }
    fragment studentData on Student{
        id
        name
        age
    }
  `;

  // the format of data
interface studentData{
   studentList:student[];
   singleStudent:student;
}

interface student{
    id: number;
    name:string;
    age: number;
}

// the format of input entered to query
interface studentDataVars {
    id: number;
}

const StudentList:React.FC<{reload:boolean}> = ({reload}) => {

    const {loading, data} = useQuery<studentData, studentDataVars>
    (getUsersQuery, {variables:{id: 1}});    

    if(!loading)
        console.log('data', data);
    
    return(
        <Table>
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Age</th>
                </tr>
            </thead>
            <tbody>
                {data?.studentList.map(({id, name, age}) => {
                    return(
                    <tr key={id}>
                        <td>{name}</td>
                        <td>{age}</td>
                    </tr>
                    )
                })}
            </tbody>
        </Table>
    );
};

export default StudentList;
```

* useMutation (Update or Insert the new data)

```javascript
import React, { useState, useEffect} from 'react';
import "../style/output.css"
import IconButton from '@material-ui/core/IconButton';
import AddCircleIcon from '@material-ui/icons/AddCircle';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import DialogActions from '@material-ui/core/DialogActions';
import TextField from '@material-ui/core/TextField';
import { Button } from 'reactstrap';
import { gql, useMutation } from "@apollo/client";

const StudentEditor:React.FC<{setReload:()=>void}> = ({setReload}) => {
    const [name, setName] = useState<string>("");
    const [age, setAge] = useState<number> (0);
    const [createDialogOpen, setCreateDialogOpen] = useState<boolean>(false);
    
    //Define the query for insertion
    const ADD_STUDENT = gql`
    mutation AddNewStudent($input: PostStudent!){
        submitStudent(input: $input){
            name
        }
    }
`;
    //Define the format of callback data
    interface studentData{
        name:string
    }

    //Define the format of user input entered to query
    interface input{
        name:string;
        age:number;
    }

    const [addStudent, {data}] = useMutation<{submitStudent:studentData},{input:input}>
    (ADD_STUDENT,{variables:{input:{name:name,age:age}}});

    const submitStudent = () =>{
        console.log("name", name," age ", age);
        addStudent();
        window.location.reload();
        setCreateDialogOpen(false);
    }
    
    if(data){
        console.log('data',data,"saved");
    }

    return (
        <div className=" flex flex-col items-center justify-center">
            <IconButton onClick={()=>setCreateDialogOpen(true)}>
                <AddCircleIcon/>
            </IconButton>         
            <Dialog open={createDialogOpen} onClose={()=>setCreateDialogOpen(false)}>
                <DialogTitle>
                    Please Enter Your Information !!!
                </DialogTitle>
                <DialogContent>
                    <TextField  label="Name" onChange={(event)=>{setName(event.target.value)}} />
                </DialogContent>   
                <DialogContent>
                    <TextField  label="Age" onChange={(event)=>{setAge(parseInt(event.target.value))}} type="number" />
                </DialogContent>
                <DialogActions>
                    <Button onClick={submitStudent}>
                        <span>Submit</span>
                    </Button>
                </DialogActions>
            </Dialog>     
        </div>
    )
}
```

### Result

![](/files/-MLuPQKJXs5DLFAn5gZ3)

![](/files/-MLuPWUkARSRpXl1aoQ6)

## REST API + GraphQL

* we can let rest api as a datasource, reformat the format of data by using graphql

### Pre-Action

```bash
npm install apollo-datasource-rest
```

### Data Source

```javascript
import {RESTDataSource} from "apollo-datasource-rest";

class StudentAPI extends RESTDataSource {
    constructor(){
      super();
      this.baseURL = 'http://localhost:8081';
    }
  
    async getStudentList(){
      const data = await this.get("/getAll");
      return data;
    }

    async insertNewStudent(name, age, className){
        const data = await this.post("/insert", {
          name: name,
          age: age,
          className: className
        })
        return data;
    }
}

export default StudentAPI;
```

### &#x20;Schema

```javascript
import { gql } from "apollo-server-express";

const schema = gql`
 
  input PostStudentHobby{
    name: String!
    age: Int!
    className: String!
  }

  type StudentWithHobby{
    id: String!
    name: String!
    className: String!
    hobbyList: [String]
  }

  extend type Query {
    test: [StudentWithHobby]
  }

  extend type Mutation{
    submitStudentWithHobby(input: PostStudentHobby!): String
  }

`;

export default schema;
```

### Resolver

```javascript
const resolvers = {
    Query:{
      test: async(parent, arg, {dataSources}) => { 
        const data = await dataSources.StudentAPI.getStudentList();
        return data;
      }
    },
    Mutation:{
      submitStudentWithHobby: async(parent, {input}, {dataSources}) => {
        const data = await dataSources.StudentAPI.insertNewStudent(input.name, input.age, input.className);
        return data;
      }
    }
};

export default resolvers;
```

### Integration

```javascript
import express from "express";
const app = express();
import cors from "cors";
import bodyParser from "body-parser";
import  { ApolloServer } from "apollo-server-express";
import schema from "./graphql/schema/index.js";
import resolvers from "./graphql/resolver/index.js";
import StudentAPI from "./graphql/test.js"

app.use(
    cors({
      origin: "*",
      methods: ["GET", "POST"],
      allowedHeaders: "Content-Type, Authorization, X-Requested-With",
    })
);

app.use(bodyParser.json());

const server = new ApolloServer({
  typeDefs: schema,
  resolvers,
  dataSources: () => ({
    StudentAPI: new StudentAPI()
  })
});
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://petercheng7788.gitbook.io/developer-note/web-communication/graphql/implementation-nodejs-+-react.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
