GraphQL is trending binnen de wereld van API ontwikkeling. Hoewel RESTful API’s nog steeds de meest populaire manier blijven om data van applicaties bloot te stellen (te exposen), kennen ze tegelijkertijd veel beperkingen die GraphQL wil oplossen.

GraphQL is een door Facebook gecreëerde querytaal, die in 2015 werd omgezet in een open-source project. De taal biedt een intuïtieve en flexibele syntaxis voor het beschrijven en openen van gegevens in een API.

Deze gids zal onderzoeken hoe je een GraphQL Node.js project kunt bouwen. We zullen GraphQL gebruiken om een Todo applicatie te bouwen in het Express.js webframework voor Node.

Wat is GraphQL?

Uit de officiële documentatie: “GraphQL is een querytaal voor API’s en een runtime voor het vervullen van die query’s met je bestaande gegevens. GraphQL biedt een volledige en begrijpelijke beschrijving van de gegevens in je API, geeft clients de mogelijkheid om precies te vragen wat ze nodig hebben en niets meer, maakt het gemakkelijker om API’s in de loop der tijd te evolueren, en maakt krachtige developertools mogelijk.”

GraphQL is een server-side runtime voor het uitvoeren van query’s met behulp van het type systeem dat je voor je gegevens hebt gedefinieerd. GraphQL is ook niet gebonden aan een specifieke database of opslag-engine. In plaats daarvan wordt het ondersteund door je bestaande code en gegevensopslag. Een gedetailleerde vergelijking van deze technologieën vind je in de GraphQL vs. RESTful API gids.

Om een GraphQL dienst te maken, begin je met het definiëren van schema types en het creëren van velden die die types gebruiken. Vervolgens geef je een functie-resolver die op elk veld en type wordt uitgevoerd wanneer gegevens door de client worden opgevraagd.

GraphQL terminologie

Het GraphQL typesysteem wordt gebruikt om te beschrijven welke gegevens kunnen worden opgevraagd en welke gegevens je kunt manipuleren. Dit is de kern van GraphQL. Laten we verschillende manieren bespreken waarop we gegevens kunnen beschrijven en manipuleren in GraphQ.

Objecttypen

GraphQL objecttypen zijn gegevensmodellen die sterk getypeerde velden bevatten. Er moet een 1-op-1 mapping zijn tussen je modellen en GraphQL types. Hieronder staat een voorbeeld van GraphQL Type:

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

Query’s

GraphQL Query definieert alle query’s die een klant kan uitvoeren op de GraphQL API. Je moet een RootQuery definiëren die volgens afspraak alle bestaande query’s bevat.

Hieronder definiëren en mappen we de query’s naar de bijbehorende 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
}

Mutaties

Als GraphQL Queries GET verzoeken zijn, dan zijn mutaties POST, PUT, PATCH, en DELETE verzoeken die de GraphQL API manipuleren.

We zetten alle mutaties in een enkele RootMutation om te demonstreren:

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
}

Je merkte waarschijnlijk al op dat we -input types gebruiken voor de mutaties, zoals UserInput, TodoInput . Het is altijd een best practice om altijd Input types te definiëren bij het maken en updaten van je resources.

Je kunt de invoertypes definiëren zoals hieronder:

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

Resolvers

Resolvers vertellen GraphQL wat er moet gebeuren bij het opvragen van een query of mutatie. Het is een basisfunctie die het zware werk doet om de databaselaag aan te spreken om de CRUD (create, read, update, delete) operaties uit te voeren, een intern RESTful API endpoint aan te spreken, of een microservice te callen om het verzoek van de client te vervullen.

Je kunt een nieuw resolvers.js bestand maken en de volgende code toevoegen:

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 schema is wat GraphQL blootstelt aan de wereld. Daarom zullen de types, query’s en mutaties in het schema worden opgenomen om aan de wereld te worden blootgesteld.

Hieronder staat hoe je types, queries en mutaties aan de wereld blootstelt:

schema {
  query: RootQuery
  mutation: RootMutation
}

In het bovenstaande script hebben we de eerder gemaakte RootQuery en RootMutation opgenomen om te exposen.

Hoe werkt GraphQL met Node.js en Express.js?

GraphQL biedt een implementatie voor alle grote programmeertalen, en Node.js is daar niet van uitgezonderd. Op de officiële GraphQL website is er een sectie voor ondersteuning van JavaScript, en ook zijn er andere implementaties van GraphQL om het schrijven en coderen eenvoudig te maken.

GraphQL Apollo biedt een implementatie voor Node.js en Express.js en maakt het eenvoudig om met GraphQL aan de slag te gaan.

In de volgende paragraaf leer je hoe je je eerste GraphQL applicatie in Node.js en Express.js backend framework kunt maken en ontwikkelen met GraphQL Apollo..

GraphQL opzetten met Express.js

Het bouwen van een GraphQL API server met Express.js is eenvoudig om mee te beginnen. In deze sectie zullen we onderzoeken hoe je een GraphQL server bouwt.

Project initialiseren met Express

Eerst moet je een nieuw Express.js project installeren en opzetten. Maak een map aan voor je project en installeer Express.js met dit commando:

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

Bovenstaand commando maakt een nieuw package.json bestand aan en installeert de Express.js bibliotheek in je project.

Vervolgens gaan we ons project structureren zoals in de afbeelding hieronder. Het zal verschillende modules bevatten voor de functies van het project, zoals gebruikers, todo’s, enz.

Een lijst met bestanden in graphql-todo.
Bestanden voor graphql-todo.

GraphQL initialiseren

Laten we beginnen met het installeren van de GraphQL Express.js dependencies. Voer het volgende commando uit om te installeren:

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

Schema’s en types aanmaken

Vervolgens maken we een bestand index.js aan in de map modules en voegen we het volgende stukje code toe:

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;

Uitleg code

Laten we het codefragment doornemen en bij langsgaan:

Stap 1

Eerst hebben we de vereiste bibliotheken geïmporteerd en standaard query- en mutatietypen aangemaakt. De query en mutatie stellen voorlopig alleen de versie van de GraphQL API in. We zullen de query en mutatie echter uitbreiden met andere schema’s als we verder gaan.

Een opdrachtregelinterface met de "const" code voor het importeren van GraphQL en andere extensies.
Het importeren van GraphQL en extensies.
Stap 2:

Vervolgens maakten we een nieuw scalar type voor de tijd en onze eerste resolver voor de hierboven gemaakte query en mutatie. Daarnaast hebben we ook een schema gegenereerd met de functie makeExecutableSchema.

Het gegenereerde schema bevat alle andere schema’s die we importeerden en zal er ook meer bevatten naarmate we ze aanmaken en importeren.

Een opdrachtregelinterface met de const code voor het maken van ons scalar type en onze eerste resolver.
Het aanmaken van een scalar type voor tijd en onze eerste resolver.

Het bovenstaande codefragment laat zien dat we verschillende schema’s hebben geïmporteerd in de makeExecutableSchema functie. Deze aanpak helpt ons bij het structureren van de applicatie met het oog op complexiteit. Vervolgens gaan we de geïmporteerde Todo en User schema’s aanmaken.

Todo schema maken

Het Todo schema toont eenvoudige CRUD operaties die gebruikers van de applicatie kunnen uitvoeren. Hieronder staat het schema dat de Todo CRUD operatie implementeert.

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 };

Uitleg code

Laten we het codefragment doornemen:

Stap 1:

Eerst hebben we een schema gemaakt voor onze Todo met GraphQL type, input, en extend. Het keyword extend wordt gebruikt om nieuwe query’s en mutaties te inheriten en toe te voegen aan de bestaande hoofdquery en mutatie die we hierboven hebben gemaakt.

Een opdrachtregelinterface met het schema voor ons Todo-script, inclusief nieuwe invoer.
Het maken van het schema voor onze Todo.
Stap 2:

Vervolgens hebben we een resolver gemaakt, die wordt gebruikt om de juiste gegevens op te halen als een bepaalde query of mutatie wordt gecalld.

Een opdrachtregelinterface met de code om een resolver voor onze Todo te maken.
Een resolver maken.

Met de nu ingestelde resolverfunctie kunnen we individuele methoden maken voor de bedrijfslogica en database-manipulatie, zoals te zien is in het create-todo.js voorbeeld.

Maak een bestand create-user.js in de map ./mutations en voeg de bedrijfslogica toe om een nieuwe Todo in je database aan te maken.

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

Het bovenstaande codefragment is een vereenvoudigde manier om een nieuwe Todo in onze database aan te maken met behulp van de Sequelize ORM. Je kunt meer leren over Sequelize en hoe je het instelt met Node.js.

Je kunt dezelfde stap volgen om vele schema’s te maken, afhankelijk van je applicatie, of je kunt het complete project klonen van GitHub.

Vervolgens gaan we de server opzetten met Express.js en de nieuw gemaakte Todo applicatie draaien met GraphQL en Node.js.

Instellen en draaien van de server

Tot slot gaan we onze server opzetten met de apollo-server-express bibliotheek die we eerder installeerden en configureren.

De apollo-server-express is een eenvoudige wrapper van Apollo Server voor Express.js, hij wordt aanbevolen omdat hij ontwikkeld is om in Express.js development te passen.

Laten we met behulp van de voorbeelden die we hierboven hebben besproken, de Express.js server configureren om te werken met de nieuw geïnstalleerde apollo-server-express.

Maak een server.js bestand in de hoofdmap en plak er de volgende code in:


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`)
);

In bovenstaande code heb je met succes je eerste CRUD GraphQL server voor Todo’s en Gebruikers gemaakt. Je kunt je ontwikkelingsserver starten en je testproject openen met http://localhost:3000/graphql. Als alles gelukt is, zou je onderstaand scherm moeten krijgen:

Een ontwikkelingsinterface die een eenvoudige query als respons toont.
Het verificatiescherm.

Samenvatting

GraphQL is een door Facebook ondersteunde moderne technologie die het vervelende werk vereenvoudigt van het maken van grootschalige API’s met RESTful architectuurpatronen.

In deze gids hebben we je meer verteld over GraphQL en laten zien hoe je je eerste GraphQL API kunt ontwikkelen met Express.js.

Laat ons weten wat jij aan het bouwen bent met GraphQL in onderstaande comments.

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