Implementation (NodeJS + React)
Server-Side (NodeJS Express)
Pre-Action
npm install apollo-server-express
Structure
Data
Define the data source
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)
// 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;
//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
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)
// 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;
// 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
import studentResolvers from './student.js';
import courseResolvers from './course.js';
export default [studentResolvers, courseResolvers];
Output
Group data, schema and resolver together
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
Client-Side (React)
Pre-Action
npm install @apollo/client
Configuration
import {ApolloClient, InMemoryCache} from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:8080/graphql',
cache: new InMemoryCache()
});
export default client;
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)
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)
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
REST API + GraphQL
we can let rest api as a datasource, reformat the format of data by using graphql
Pre-Action
npm install apollo-datasource-rest
Data Source
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;
Schema
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
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
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()
})
});
Last updated
Was this helpful?