Initialize & install dependencies

npm init -y
npm i express graphql express-graphql -s
Copy the code

server.js

const express = require('express')
const {buildSchema} = require('graphql')
const {graphqlHTTP} = require('express-graphql')

// Define schema queries and types
const schema = buildSchema(` type Query { hello: String } `)

// Define the handler for the query
const rootValue = {
  hello: () = > {
    return 'hello world! '}}const app = express()

app.use('/graphql', graphqlHTTP({
  schema,
  rootValue,
  graphiql: true,
}))

app.listen(3000.function(){
  console.log('Start listening on port 3000');
})
Copy the code

Running server

nodemon server.js
Copy the code

GraphqlAPI test

A simple type

localhost:3000/graphql
Copy the code

The text was translated by Google

Type Hello, click ▶ Run, and return to the center

You can increase the number of schemas and return values to query for more data

// Define schema queries and types
const schema = buildSchema(` type Query { hello: String userName: String age: Int } `)

// Define the handler for the query
const root = {
  hello: () = > {
    return 'hello world! '
  },
  userName: () = > {
    return 'Max'
  },
  age: () = > {
    return 18}}Copy the code

You can refresh the test page to query the new data

If a piece of data is not needed, it can be removed to reduce the amount of data to be returned

The complex type

When you want to return data with nested objects, you need to define a type for the internal object

Within the schema, type defines the object type, nested within Query, and root returns this object through a function below, even if not all of the front-end requests are returned

// Define schema queries and types
const schema = buildSchema(` type Contact { tel: Int qq: Int signal: String } type Query { hello: String userName: String age: Int contact: Contact } `)

// Define the handler for the query
const root = {
  hello: () = > {
    return 'hello world! '
  },
  userName: () = > {
    return 'Max'
  },
  age: () = > {
    return 18
  },
  contact: () = > {
    return {
      tel: '13923456789'.qq: 279123456.signal: 'Maxuan'}}}Copy the code

I’m intentionally miswriting a tel as an integer Int, but I’m returning a string. Let’s see what happens

Test page refresh click on the right side of the query will find more contact, the left side of the input contact to run

Tel returns null due to a deliberate error above. The returned value object contains not only data, but also errors, containing error information, location, and directory

Strong type checking helps us avoid errors at the development stage

Click Contact on the right to view the nested object type. If the required data is not all, you can only write the required data when requested

Parameter type & pass parameter

The parameter types

  • The basic types of arguments are String, Int, Float, Boolean, and ID, which can be used directly when shema is declared

ID is essentially a string, unique in the database. When ID is used as a parameter type, an error will be reported if duplicate values occur

  • [type] represents an array, for example, [Int] represents an array whose members are all integers

Parameter passing

  • As with js parameters, parameters are defined in parentheses, but note that parameters need to define types

  • ! The value of the type cannot be null; otherwise, an error will be reported

  • Query is the Query entry to the schema

    type Query { rollDice(number: Int! , numSides: Int): [Int] }Copy the code

    When defining an interface, you need to determine which parameters must be passed, plus those that must be passed. The return value is an array of pure integer members

Query statement

When required, the query passes parameters to the function, the parameters are mandatory, and the type definition returns an array of pure string members

// Define schema queries and types
const schema = buildSchema(` type Query { getClassMembers(classNum: Int!) : [String] } `)

// Define the handler for the query
const root = {
  getClassMembers({classNum}) {
    const obj = {
      31: ['Joe'.'bill'.'Cathy'].61: ['Zhang Xiaosan'.'Li Xiao Si'.'Wang Xiaowu']}return obj[classNum]
  }
}
Copy the code

The client queries through the method, which tests the error message returned if no parameter is passed

Error 1

Error 2

If I pass null

Mistake 3

The query result does not exist

Right back

Get data by passing the correct parameters

Parameter transmission for complex types

Server query statement

// Define schema queries and types
const schema = buildSchema(` type Student { name: String age: Int gender: String subject(fraction: Int!) : [String] } type Query { getClassMembers(classNum: Int!) : [String] student(studentName: String): Student } `)

// Define the handler for the query
const root = {
  getClassMembers({classNum}) {
    const obj = {
      31: ['Joe'.'bill'.'Cathy'].61: ['Zhang Xiaosan'.'Li Xiao Si'.'Wang Xiaowu']}return obj[classNum]
  },
  student({studentName}) {
    return{ 
      name: studentName,
      age: 18.gender: 'male'.subject: ({fraction}) = > {
        if(fraction > 80) {
          return ['mathematics'.'physical'.'chemistry']}else {
          return ['mathematics'.'physical'.'chemistry'.'Chinese'.'history'.'geographic']}}}}}Copy the code

Client query statement (the client query statement does not support single quotation marks). When the calling method passes in parameters to query, attributes that do not need to be queried are automatically completed

Internal a function that needs to pass the parameter, call and pass the parameter, get the return data

The client accesses GraphQL Clients

The client accesses the GraphQL interface

Write and create the public/index.html entry file in server.js

// Public folder to access static resources
app.use(express.static('public'))
Copy the code

fetch API

<body>
  <button onclick="getStudent()">To get the data</button>
</body>
<script>
  const variables = { studentName: "Max".fraction: 90};
  const query = ` query student($studentName: String, $fraction: Int!) { student(studentName: $studentName) { name age gender subject(fraction: $fraction) } } `;
  function getStudent() {
    fetch("/graphql", {
      method: "POST".// All graphQL requests must be POST
      headers: {
        "Content-Type": "application/json".Accept: "application/json",},body: JSON.stringify({
        query,
        variables
      }),
    }).then(res= > res.json())
      .then(data= > {
      console.log(data); })}</script>
Copy the code

Declare the query statement query and the parameter variables inside the function. These are convention variables that cannot be changed. Pass in the parameter of type Query Student, starting with $, and the type is consistent with the schema declaration, if any! I don’t want to miss this, and then pass it to the student object that returns the data

const query = ` query Student($studentName: String, $fraction: Int!) { student(studentName: $studentName) { name age gender subject(fraction: $fraction) } } `;
Copy the code

Click the button to get the data

Axios API

Query query statement

<body>
  <button onclick="getStudent()">To get the data</button>
</body>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
  const variables = { studentName: "Max".fraction: 90};
  const query = ` query Student($studentName: String, $fraction: Int!) { student(studentName: $studentName) { name age gender subject(fraction: $fraction) } } `;
  function getStudent(){
    axios({
      method: "POST".url: "/graphql".data:{
        query,
        variables
      }
    }).then(res= > {
      console.log(res.data); })}</script>
Copy the code

Mutation change statement

The mutation statement is also passed in as a variable name named query. The mutation statement can be omitted from the query statement

const variables = {userInput: {name: "Maxxx".age: 25.gender: "male"}}
const query = ` mutation createUser($userInput: userInput){ createUser(input: $userInput) { name } } `;
function getStudent(){
  axios({
    method: "POST".url: "/graphql".data:{
      query,
      variables
    }
  }).then(res= > {
    console.log(res.data); })}Copy the code

Add & modify data Mutation

Code written

The data is queried using Query, the data is added, modified, and deleted using Mutation, and the received parameter is an object that cannot be defined by type but must be an input type

// Define schema queries and types
// To distinguish between input and type, this does not mean that the input value is the same as the return value
const schema = buildSchema(` input userInput { name: String age: Int gender: String } type User{ name: String age: Int gender: String } type Mutation { createUser(input: userInput): User updateUser(id: ID! , input: userInput): User } `)

// Virtual database
const fakeDB = {}

// Define the handler for the query
const root = {
  createUser({input}) {
    // Save the database
    fakeDB[input.name] = input
    // Returns the save result
    return fakeDB[input.name]
  },
  updateUser({id, input}) {
    // Database update
    const update = Object.assign({},fakeDB[id], input)
    fakeDB[id] = update
    // Returns the save result
    return fakeDB[id]
  }
}
Copy the code

troubleshooting

Run the server and refresh the test page to see loading, no mode displayed

This is a GraphQL pit that requires a Query Query within the schema, even if it only does add and delete

Add a query that returns data to the virtual database converted to an array, refresh the test page, and you can see the pattern

Add data

Add user (s) using mutation (s). Add user (s) using mutation (s). Add user (s) using mutation

Then add another user

Query authentication

Modify the data

Next, try to modify the data

Query authentication

Authentication and Middleware

Add middleware, determine permissions, and process requests

const app = express()

const middlware = (req, res, next) = > {
  // If the request information is orientation/graphQL interface, and the request header does not contain the word auth, an error is returned
  if(req.url.indexOf('/graphql') && req.headers.cookie.indexOf('auth') = = = -1) {
    res.send(JSON.stringify({
      error: "You don't have access to this interface."
    }))
    return
  }
  next()
}

app.use(middlware)
Copy the code

Because there is no AuTH, refreshing the test page will display an error message, check the Cookie, and there is no Auth

Cookies can be added manually

Refresh the page, request exists auth, release the request, you can see that the page is accessible

Integrating with a database

Creating a database

Open the database management tool, connect to the mysql database, create the GQLtest database and table, note that the ID is set to grow automatically

server.js

Install mysql dependencies and import and configure them

npm i mysql -s
Copy the code
const mysql = require('mysql')

const pool = mysql.createPool({
  connectionLimit: 10.host: '192.168.31.11'.port: '3307'.user: 'root'.password: '123456'.database: 'gqltest'
})
Copy the code

The createUser database is added

createUser({input}) {
  return new Promise((resolve, reject) = > {
    pool.query('insert into users set ? ', input, err= > {
      if(err) {
        console.log('Wrong' + err.message)
        return
      }
      resolve(input)
    })
  })
}
Copy the code

Test page input statements

You can see that the message was returned successfully

Refresh the database and see the new data in the database

Users database query

users() {
  return new Promise((resolve, reject) = > {
    pool.query('select name, age, gender from users'.(err, res) = > {
      if(err) {
        console.log('wrong' + err.message)
        return
      }
      resolve(res)
    })
  })
}
Copy the code

Query test

UpdateUser Updates the database

updateUser({id, input}) {
  return new Promise((resolve, reject) = > {
    pool.query('update users set ? where name = ? ', [input, id], err= > {
      if(err) {
        console.log('Wrong'+ err.message)
        return
      }
      resolve(input)
    })
  })
}
Copy the code

Test statements

Viewing a Database

DeleteUser Deletes a database

deleteUser({id}) {
  return Promise((resolve, reject) = > {
    pool.query('delete from users where name = ? ', [id], err= > {
      if(err) {
        console.log('Wrong' + err.message)
        reject(false)
        return
      }
      resolve(true)})})}Copy the code

Test statements

Viewing a Database

ApolloGraphQL

introduce

Apollo is an open source GraphQL development platform, which provides GraphQL specification of the server and client implementation, using Apollo to make it easier to develop GraphQL

Apollo Graph Platform — Unify APIs, microservices, and databases into a Graph that allows you to query with GraphQL

Apollo GraphQL dead simple

The basic use

Apollo Server – Apollo GraphQL Docs

On the right sidebar

  • Step 1 – Create the project
  • Step 2 – Install dependencies
  • Step 3 — Define GraphQL Schema
  • Step 4 — Define the data set
  • Step 5 — Define a resolver
  • Step 6 — Create ApolloServer instance
  • Step 7 — Start ApolloServer
  • Step 8 — Execute the first query
  • Combination instance
  • The next step

Initialize & install dependencies

npm init -y
npm i apollo-server graphql -s
Copy the code

The Apollo GraphQL plugin is installed by vscode to implement template string highlighting

index.js

const { ApolloServer, gql } = require('apollo-server')

// Step 3 -- Define the schema
const typeDefs = gql` type Book { title: String author: String } type Query { books: [Book] } `
// Step 4 -- Define the data set
const books = [
  {
    title: 'The Awakening'.author: 'Kate Chopin'}, {title: 'City of Glass'.author: 'Paul Auster',}]// Step 5 -- define resolver
Resolver (resolver) defines how to get a schema
// The parser retrieves books from the books array above
const resolvers = {
  // All queries are here
  Query: {
    books: () = > books
  }
}

// Step 6 -- Create the two parameters required by ApolloServer
const server = new ApolloServer({ typeDefs, resolvers })


// Step 7 -- the listen method starts a network server with the default localhost:4000
server.listen().then(({ url }) = > {
  console.log(` 🚀 Server ready at${url}`)})Copy the code

Start the server

nodemon index.js
Copy the code

Open the localhost: 4000

Click the button to enter the query test page test statement, the test page is more powerful, easy to use, no longer need to manually input, just need to click the left corresponding query statement + to add variables in accordance with the structure

Use with Express

ApolloServer itself provides web service functionality, but in practice it is recommended to use it in conjunction with mainstream server frameworks when developing production services

www.apollographql.com/docs/apollo…

Apollo provides server frameworks that integrate into Express and KOA for use as middleware imports

Initialize & install dependencies

npm init -y
npm i apollo-server-express apollo-server-core express graphql -s
Copy the code

index.js

const { ApolloServer, gql } = require('apollo-server-express')
const { ApolloServerPluginDrainHttpServer }  = require('apollo-server-core')
const express = require('express')
const http = require('http')

const app = express()

const typeDefs = gql` type Book { title: String author: String } type Query { books: [Book] } `
const books = [
  {
    title: 'The Awakening'.author: 'Kate Chopin'}, {title: 'City of Glass'.author: 'Paul Auster',}]const resolvers = {
  Query: {
    books: () = > books
  }
}

async function startApolloServer(typeDefs, resolvers) {
  const httpServer = http.createServer(app)
  const server = new ApolloServer({
    typeDefs,
    resolvers,
    plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
  })
  await server.start()
  server.applyMiddleware({ app })
  await new Promise(resolve= > httpServer.listen({ port: 4000 }, resolve))
  console.log('🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
} 
startApolloServer(typeDefs,resolvers)
Copy the code

Start the server

nodemon index.js
Copy the code

Query statement test

Differences from native GraphQL

There are some differences here, using examples from the official documentation

The basic grammar

There is no difference in basic grammar

const typeDefs = gql` type Query { numberSix: Int! # Should always return the number 6 when queried numberSeven: Int! # Should always return 7 } `

const resolvers = {
  Query: {
    numberSix() {
      return 6
    },
    numberSeven() {
      return 7}}}Copy the code
Processing parameters

The following code wants to query the user by the ID field and defines an array as a queryable object

const typeDefs = gql` type User { id: ID! name: String } type Query { user(id: ID!) : User } `

const users = [
  { id: '1'.name: 'Elizabeth Bennet'},
  { id: '2'.name: 'Fitzwilliam Darcy'}]const resolvers = {
  Query: {
    user(parent, args, context, info) {
      return users.find(user= > user.id === args.id)
    }
  }
}
Copy the code

The Resolvers parser defined here can optionally accept four parameters

Args — Client query parameters

Just like the original ARGS

Parent — Parse chain

The libraries statement returns an array of Library objects of type Library, an array of Library objects of type Book, and an author of type Book defined by type author

const typeDefs = gql` type Library { branch: String! books: [Book!]  } type Book { title: String! author: Author! } type Author { name: String! } type Query { libraries: [Library] } `
Copy the code

The query statement for this schema is written as follows

query GetBooksByLibrary {
  libraries {
    books {
      author {
        name
      }
    }
  }
}
Copy the code

This parser chain matches the hierarchy of the query itself

The parser executes in the above order and passes the returned value to the next parser in the parse chain via parent

/ / define schema
const typeDefs = gql` type Library { branch: String! books: [Book!]  } type Book { title: String! author: Author! } type Author { name: String! } type Query { libraries: [Library] } `
// Virtual data
// Librarys defines the branch branch, which is used to query the number of books belonging to that branch and return it
const libraries = [
  {branch: 'downtown'},
  {branch: 'riverside'},]const books = [
  {
    title: 'The Awakening'.author: 'Kate Chopin'.branch: 'riverside'
  },
  {
    title: 'City of Glass'.author: 'Paul Auster'.branch: 'downtown'},]/ / the parser
const resolvers = {
  Query: {
    libraries() {
      return libraries
    }
  },
  Library: {
    books(parent) {
      return books.filter(book= > book.branch === parent.branch)
    }
  },
  Book: {
    author(parent) {
      return {
        name: parent.author
      }
    }
  }
}
Copy the code

The parse chain looks like this: the query parameters at the upper level are passed to the lower level of the query by parent, and if there are multiple levels, they are passed down

Context — Context object

Any GraphQL request will pass through here. This function receives the request object, receives the request body, and returns an object containing the request body data from the defined context. Each subsequent resolver can directly fetch the data from the context

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
  context (req) {
    return {
      foo: "bar"}}})Copy the code

Of course, you can’t write the context directly as a parameter, you need to write the first two, now make the request again, the server will print out the object

const resolvers = {
  Query: {
    libraries(parent, args, context) {
      console.log(context)
      return libraries
    }
  }
}
Copy the code

For example, when the client initiates a request corresponding to the operation requiring permissions, the user’s login information is transmitted at the same time. ApollowServer will put the data of user login information carried by the request body into the context after receiving the request, and the following resolver can obtain and judge it

Get data from MongoDB

Data sources – Apollo Server – Apollo GraphQL Docs

There are many ways to obtain data in resolver, including mapping data requests from RESTful apis into ApolloServer’s GRPHQL Schema. It is suitable for migrating traditional apis to GraphQL, leaving the original RESTful apis intact. Mapping to do a process can be developed and used

Node-mongose.js – Digging gold (juejin. Cn)

Install dependencies

npm i mongoose -s
Copy the code

Introduce modules & connect to the database

Create the Models directory

Models /index.js introduces the Mongoose module and connects to the database

const mongoose = require('mongoose')

// Connect, configure the database, return a pending connection
mongoose.connect('mongo: / / 192.168.31.107:27017', { 
  user: 'maxuan'.pass: '601109'.dbName: 'blog'.autoIndex: false.useNewUrlParser: true
})
// Get the database object
const db = mongoose.connection
// Warning of connection failure
db.on('error'.console.error.bind(console.'connection error:'))
// The connection succeeded
db.once('open'.async() = > {console.log("Database connection successful!")})// Introduce the Users model and throw it
module.exports = {
  users: require('./users')}Copy the code

Models /users.js import the Mongoose module, declare the Mongoose schema to be compiled into a model and throw it, import it in models/index.js

const mongoose = require('mongoose')

const usersSchema = new mongoose.Schema({
  name: String.age: Number.gender: String
})

// Compile usersSchema into a model
module.exports = mongoose.model("users", usersSchema)
Copy the code

Index. js introduces models/index.js and deconstructs the Users model of Mongoose, which can be used to connect to the database

const { users } = require('./models')

const typeDefs = gql` type User { _id: ID name: String, age: Int gender: String } type Query { users: [User!] } `

const resolvers = {
  Query: {
    async users () { // After receiving the query request, initiate a query to the Users of Mongoose
      return await users.find()
    }
  }
}
Copy the code

Start the server

nodemon index.js
Copy the code

Query test

Query a single user by ID

const { users } = require('./models')

const typeDefs = gql` type User { _id: ID name: String, age: Int gender: String } type Query { users: [User!]  user(id: ID!) : User } `

const resolvers = {
  Query: {
    async users () {
      return await users.find()
    },
    async user (parent, { id }) {
      return await users.findById(id)
    }
  }
}
Copy the code

Check generate query, check Arguments ID, enter userId below, run query, return results

Use DataSources to get data

The above method of manipulating the database from the Resolvers is actually not recommended because the resolvers and the database are so tightly coupled that if the current data is returned to the client through the RESTful API mapping, the code will definitely need to be retuned

It is recommended to encapsulate the database-related operation code

Github’s website provides examples and documentation

GraphQLGuide/apollo-datasource-mongodb: Apollo data source for MongoDB (github.com)

Install dependencies

npm i apollo-datasource-mongodb
Copy the code

This package uses DataLoader to cache batches and each request, and optionally to do shared application caching (if TTL is provided) (using the default Apollo InMemoryLRUCache or the cache provided to ApolloServer())

It caches the following API requests:

  • findOneById(id, options)
  • findManyById(ids, options)
  • findByFields(fields, options)

Usage basis

The basic setup is to subclass the MongoDataSource, pass the collection or Mongoose model to the constructor, and use API methods to query the data by ID

Create a data – sources/Users. Js

Note that the findOneById() API is used directly for this

const { MongoDataSource } = require('apollo-datasource-mongodb')

class Users extends MongoDataSource {
  getUser(userId) {
    return this.findOneById(userId)
  }
}

module.exports = Users
Copy the code

Index.js imports this subclass

Add dataSources to ApolloServer. This function returns an object that instantiates the Users class in data-sources/ users.js and passes in the Mongoose model

const Users = require('./data-sources/Users')



// Add dataSources to ApolloServer
const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
  context (req) {
    return{}},dataSources() {
    return {
      users: new Users(users)
    }
  }
})
Copy the code

Resolvers syntax changed

Users has a getUser method inside the Users instance and takes an ID as an argument

Instead of working with the database in resolvers, you call methods in subclasses of data-sources in resolvers

const resolvers = {
  Query: {
    async user (parent, { id }, { dataSources }) {
      return await dataSources.users.getUser(id)
    }
  }
}
Copy the code

Access data model objects through this.model

DataSources only provides the above three apis to cache data to handle requests, while mongoose methods are used for other database operations

Add additional methods to operate on the database in the data-sources/ users.js subclass

const { MongoDataSource } = require('apollo-datasource-mongodb')

class Users extends MongoDataSource {
  getUser(userId) {
    return this.findOneById(userId)
  },
  // Return all users
  getUsers() {
    return this.model.find()
  }
}

module.exports = Users
Copy the code

(parent, {id}, {dataSources}) parameters must be written, even if parent and id are not used

const resolvers = {
  Query: {
    async user (parent, { id }, { dataSources }) {
      return await dataSources.users.getUser(id)
    },
    async users (parent, { id }, { dataSources }) {
      return await dataSources.users.getUsers()
    }
  }
}
Copy the code

The query statement responded properly

In the future, resolvers can be defined anywhere. Resolvers get their data from a RESTful API, file, or database without any dependencies. Just call dataSources and get the data

The data of dataSources is obtained by the subclass of data-sources. If the data source changes in the future, it will be changed in the subclass. Basically, the core logic in resolvers will not be changed, which is recommended by ApolloServer