GraphQL er det nye buzzword inden for API-udvikling. Mens RESTful API’er fortsat er den mest populære måde at udstille data fra applikationer på, har de mange begrænsninger, som GraphQL har til formål at løse.

GraphQL er et forespørgselssprog, der er skabt af Facebook, og som blev omdannet til et open source-projekt i 2015. Det tilbyder en intuitiv og fleksibel syntaks til at beskrive og tilgå data i et API.

Denne vejledning vil undersøge, hvordan man opbygger et GraphQL Node.js-projekt. Vi vil bruge GraphQL til at bygge en Todo-applikation i Express.js webrammen til Node.

Hvad er GraphQL?

Fra den officielle dokumentation: “GraphQL er et forespørgselssprog til API’er og en runtime til at opfylde disse forespørgsler med dine eksisterende data. GraphQL giver en komplet og forståelig beskrivelse af dataene i din API, giver klienterne mulighed for at spørge om præcis det, de har brug for, og ikke mere, gør det lettere at udvikle API’er over tid og muliggør kraftfulde udviklerværktøjer.”

GraphQL er en server-side runtime til udførelse af forespørgsler ved hjælp af det typesystem, du har defineret for dine data. GraphQL er heller ikke bundet til nogen specifik database eller lagringsmotor. I stedet bakkes den op af din eksisterende kode og datalagring. Du kan få en detaljeret sammenligning af disse teknologier med GraphQL vs. RESTful API-guiden.

Hvis du vil oprette en GraphQL-tjeneste, starter du med at definere skematyper og oprette felter ved hjælp af disse typer. Dernæst leverer du en funktionsopløser, der skal udføres på hvert felt og hver type, når der anmodes om data fra klientsiden.

GraphQL-terminologi

GraphQL-typesystemet bruges til at beskrive, hvilke data der kan forespørges, og hvilke data du kan manipulere. Det er kernen i GraphQL. Lad os diskutere forskellige måder, hvorpå vi kan beskrive og manipulere data i GraphQ.

Objekttyper

GraphQL-objekttyper er datamodeller, der indeholder stærkt skrevne felter. Der skal være en 1-til-1-mapping mellem dine modeller og GraphQL-typer. Nedenfor er et eksempel på en GraphQL-type:

type User {
  id: ID! # The "!" means required
  firstname: String
  lastname: String
  email: String
  username: String
  todos: [Todo] # Todo is another GraphQL type
}

Queries

GraphQL Query definerer alle de forespørgsler, som en klient kan køre på GraphQL API’et. Du bør definere en RootQuery, som vil indeholde alle eksisterende forespørgsler som en konvention.

Nedenfor definerer og mapper vi forespørgslerne til den tilsvarende RESTful API:

type RootQuery {
  user(id: ID): User           # Corresponds to GET /api/users/:id
  users: [User]                # Corresponds to GET /api/users
  todo(id: ID!): Todo    # Corresponds to GET /api/todos/:id
  todos: [Todo]          # Corresponds to GET /api/todos
}

Mutations

Hvis GraphQL-forespørgsler er GET -forespørgsler, er mutationer POST, PUT, PATCH og DELETE -forespørgsler, der manipulerer GraphQL API.

Vi vil placere alle mutationer i en enkelt RootMutation for at demonstrere dem:

type RootMutation {
  createUser(input: UserInput!): User             # Corresponds to POST /api/users
  updateUser(id: ID!, input: UserInput!): User    # Corresponds to PATCH /api/users
  removeUser(id: ID!): User                       # Corresponds to DELETE /api/users
  createTodo(input: TodoInput!): Todo
  updateTodo(id: ID!, input: TodoInput!): Todo
  removeTodo(id: ID!): Todo
}

Du har bemærket brugen af -input typer til mutationerne såsom UserInput, TodoInput. Det er altid den bedste praksis altid at definere inputtyper til oprettelse og opdatering af dine ressourcer.

Du kan definere inputtyperne som nedenfor:

input UserInput {
  firstname: String!
  lastname: String
  email: String!
  username: String!
}

Resolvers

Resolvers fortæller GraphQL, hvad der skal ske, når der anmodes om hver forespørgsel eller mutation. Det er en grundlæggende funktion, der gør det hårde arbejde med at ramme databaselaget for at udføre CRUD-operationer (oprette, læse, opdatere, slette), ramme et internt RESTful API endpoint eller kalde en microservice for at opfylde klientens anmodning.

Du kan oprette en ny resolvers.js-fil og tilføje følgende kode:

import sequelize from '../models';
export default function resolvers () {
  const models = sequelize.models;
  return {
    // Resolvers for Queries
    RootQuery: {
      user (root, { id }, context) {
        return models.User.findById(id, context);
      },
      users (root, args, context) {
        return models.User.findAll({}, context);
      }
    },
    User: {
      todos (user) {
        return user.getTodos();
      }
    },
  }
  // Resolvers for Mutations
  RootMutation: {
    createUser (root, { input }, context) {
      return models.User.create(input, context);    
    },
    updateUser (root, { id, input }, context) {
      return models.User.update(input, { ...context, where: { id } });
    },
    removeUser (root, { id }, context) {
      return models.User.destroy(input, { ...context, where: { id } });
    },
    // ... Resolvers for Todos go here
  }
}

Schema

GraphQL-skemaet er det, som GraphQL eksponerer for verden. Derfor vil typer, forespørgsler og mutationer blive inkluderet i skemaet for at blive eksponeret for verden.

Nedenfor kan du se, hvordan du eksponerer typer, forespørgsler og mutationer for verden:

schema {
  query: RootQuery
  mutation: RootMutation
}

I ovenstående script har vi inkluderet RootQuery og RootMutation, som vi har oprettet tidligere, og som skal eksponeres for verden.

Hvordan fungerer GraphQL med Node.js og Express.js?

GraphQL tilbyder en implementering til alle større programmeringssprog, og Node.js er ikke undtaget. På det officielle GraphQL-websted er der en sektion for JavaScript-understøttelse, og der er også andre implementeringer af GraphQL for at gøre det nemt at skrive og kode.

GraphQL Apollo tilbyder en implementering til Node.js og Express.js og gør det nemt at komme i gang med GraphQL.

Du vil lære at oprette og udvikle din første GraphQL-applikation i Node.js og Express.js backend-ramme ved hjælp af GraphQL Apollo i næste afsnit.

Opsætning af GraphQL med Express.js

Det er ligetil at opbygge en GraphQL API-server med Express.js for at komme i gang. I dette afsnit vil vi undersøge, hvordan man opbygger en GraphQL-server.

Initialisér projekt med Express

Først skal du installere og opsætte et nyt Express.js-projekt. Opret en mappe til dit projekt, og installer Express.js ved hjælp af denne kommando:

cd <project-name> && npm init -y
npm install express

Ovenstående kommando opretter en ny package.json-fil og installerer Express.js-biblioteket i dit projekt.

Dernæst strukturerer vi vores projekt som vist i billedet nedenfor. Det vil indeholde forskellige moduler til projektets funktioner som f.eks. brugere, todos osv.

En liste over filer i graphql-todo.
Filer til graphql-todo.

Initialisere GraphQL

Lad os starte med at installere GraphQL Express.js-afhængighederne. Kør følgende kommando for at installere:

npm install apollo-server-express graphql @graphql-tools/schema --save

Oprettelse af skemaer og typer

Dernæst opretter vi en index.js-fil i mappen modules og tilføjer følgende kodestump:

const { gql } = require('apollo-server-express');
const users = require('./users');
const todos = require('./todos');
const { GraphQLScalarType } = require('graphql');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const typeDefs = gql`
  scalar Time
  type Query {
    getVersion: String!
  }
  type Mutation {
    version: String!
  }
`;
const timeScalar = new GraphQLScalarType({
  name: 'Time',
  description: 'Time custom scalar type',
 serialize: (value) => value,
});
const resolvers = {
  Time: timeScalar,
  Query: {
    getVersion: () => `v1`,
  },
};
const schema = makeExecutableSchema({
  typeDefs: [typeDefs, users.typeDefs, todos.typeDefs],
  resolvers: [resolvers, users.resolvers, todos.resolvers],
});
module.exports = schema;

Kode gennemgang

Lad os gennemgå kodeudsnittet og bryde det ned:

Trin 1

Først importerede vi de nødvendige biblioteker og oprettede standard forespørgsels- og mutationstyper. Forespørgsels- og mutationstyperne indstiller kun versionen af GraphQL API’et for nu. Vi vil dog udvide forespørgslen og mutationen til at omfatte andre skemaer, efterhånden som vi fortsætter.

En kommandolinjegrænseflade, der viser "const"-koden til import af GraphQL og andre udvidelser.
Import af GraphQL og udvidelser.
Trin 2:

Derefter oprettede vi en ny scalartype for tid og vores første resolver til den forespørgsel og mutation, der er oprettet ovenfor. Derudover genererede vi også et skema ved hjælp af funktionen makeExecutableSchema.

Det genererede skema indeholder alle de andre skemaer, vi importerede, og vil også indeholde flere, når vi opretter og importerer dem.

En kommandolinjegrænseflade, der viser "const"-koden til at skabe vores skalartype og vores første resolver.
Oprettelse af en skalar type for tid samt vores første resolver.

Ovenstående kodestump viser, at vi importerede forskellige skemaer i makeExecutableSchema-funktionen. Denne fremgangsmåde hjælper os med at strukturere applikationen med henblik på kompleksitet. Dernæst skal vi oprette de Todo- og User-skemaer, som vi importerede.

Oprettelse af Todo-skema

Todo-skemaet viser simple CRUD-operationer, som brugerne af applikationen kan udføre. Nedenfor er det skema, der implementerer Todo CRUD-operationen.

const { gql } = require('apollo-server-express');
const createTodo = require('./mutations/create-todo');
const updateTodo = require('./mutations/update-todo');
const removeTodo = require('./mutations/delete-todo');
const todo = require('./queries/todo');
const todos = require('./queries/todos');
const typeDefs = gql`
  type Todo {
    id: ID!
    title: String
    description: String
    user: User
  }
  input CreateTodoInput {
    title: String!
    description: String
    isCompleted: Boolean
  }
  input UpdateTodoInput {
    title: String
    description: String
    isCompleted: Boolean
  }  extend type Query {
    todo(id: ID): Todo!
    todos: [Todo!]
  }
  extend type Mutation {
    createTodo(input: CreateTodoInput!): Todo
    updateTodo(id: ID!, input: UpdateTodoInput!): Todo
    removeTodo(id: ID!): Todo
  }
`;
// Provide resolver functions for your schema fields
const resolvers = {
  // Resolvers for Queries
  Query: {
    todo,
    todos,
  },
  // Resolvers for Mutations
  Mutation: {
    createTodo,
    updateTodo,
    removeTodo,
  },
};
module.exports = { typeDefs, resolvers };

Gennemgang af kode

Lad os gennemgå kodeudsnittet og bryde det ned:

Trin 1:

Først oprettede vi et skema for vores Todo ved hjælp af GraphQL type, input og extend. Nøgleordet extend bruges til at arve og tilføje nye forespørgsler og mutationer til den eksisterende rodforespørgsel og mutation, som vi oprettede ovenfor.

En kommandolinjegrænseflade, der viser skemaet for vores Todo-script, inklusive nye input.
Oprettelse af skemaet for vores Todo.
Trin 2:

Dernæst oprettede vi en resolver, som bruges til at hente de korrekte data, når en bestemt forespørgsel eller mutation kaldes.

En kommandolinjegrænseflade, der viser koden til at oprette en resolver til vores Todo.
Oprettelse af en resolver.

Når resolver-funktionen er på plads, kan vi oprette individuelle metoder til forretningslogikken og databasemanipulationen som vist i eksemplet create-todo.js.

Opret en fil create-user.js i mappen ./mutations/, og tilføj forretningslogikken for at oprette en ny Todo i din database.

const models = require('../../../models');
module.exports = async (root, { input }, context) => {
  return models.todos.push({ ...input });
};

Kodestumpen ovenfor er en forenklet måde at oprette en ny Todo i vores database ved hjælp af Sequelize ORM. Du kan få mere at vide om Sequelize, og hvordan du konfigurerer det med Node.js.

Du kan følge det samme trin for at oprette mange skemaer afhængigt af din applikation, eller du kan klone det komplette projekt fra GitHub.

Dernæst skal vi opsætte serveren med Express.js og køre den nyoprettede Todo-applikation med GraphQL og Node.js.

Opsætning og kørsel af serveren

Til sidst vil vi opsætte vores server ved hjælp af apollo-server-express -biblioteket, som vi installerede tidligere, og konfigurere den.

apollo-server-express er en simpel wrapper af Apollo Server for Express.js, Det anbefales, fordi det er udviklet til at passe ind i Express.js udvikling.

Ved hjælp af de eksempler vi diskuterede ovenfor, lad os konfigurere Express.js-serveren til at arbejde med det nyligt installerede apollo-server-express.

Opret en server.js-fil i rodmappen, og indsæt følgende kode:


const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const schema = require('./modules');
const app = express();
async function startServer() {
  const server = new ApolloServer({ schema });
  await server.start();
  server.applyMiddleware({ app });
}
startServer();
app.listen({ port: 3000 }, () =>
  console.log(`Server ready at http://localhost:3000`)
);

I ovenstående kode har du med succes oprettet din første CRUD GraphQL-server til Todos og Users. Du kan starte din udviklingsserver og få adgang til legepladsen ved hjælp af http://localhost:3000/graphql. Hvis alt er lykkedes, bør du blive præsenteret for nedenstående skærm:

En udviklingsgrænseflade, der viser en simpel forespørgsel som svar.
Verifikationsskærmen.

Oversigt

GraphQL er en moderne teknologi, der understøttes af Facebook, og som forenkler det kedelige arbejde med at skabe API’er i stor skala med RESTful-arkitekturmønstre.

Denne vejledning har belyst GraphQL og demonstreret, hvordan du udvikler din første GraphQL API med Express.js.

Lad os vide, hvad du bygger ved hjælp af GraphQL i kommentarerne nedenfor.

Solomon Eseme

I am a Software Engineer and Content Creator who is geared toward building high-performing and innovative products following best practices and industry standards. I also love writing about it at Masteringbackend.com. Follow me on Twitter, LinkedIn, and About Me