GraphQL är det nya modeordet inom API-utveckling. Även om RESTful API: er fortfarande är det mest populära sättet att exponera data från applikationer, så rymmer de många begränsningar som GraphQL vill lösa.

GraphQL är ett sökfrågespråk som skapades av Facebook och som omvandlades till ett projekt med öppen källkod år 2015. Det erbjuder en intuitiv och flexibel syntax för att beskriva och komma åt data i ett API.

Den här guiden kommer att undersöka hur man bygger ett GraphQL node.js-projekt. Vi kommer att använda GraphQL för att bygga en Todo-applikation i webbramverket Express.js för Node.

Vad är GraphQL?

Det står i den officiella dokumentationen: ”GraphQL är ett sökfrågespråk för API: er och en körtid för att uppfylla dessa frågor med dina befintliga data. GraphQL ger en fullständig och begriplig beskrivning av data i ditt API, ger kunderna möjlighet att endast fråga efter exakt vad de behöver, gör det lättare att utveckla API: er över tiden och möjliggör kraftfulla utvecklarverktyg.”

GraphQL är en serverbaserad körtid för att utföra sökfrågor med hjälp av det typsystem som du har definierat för dina data. GraphQL är inte heller bundet till någon specifik databas eller lagringsmotor. Den stöds istället av din befintliga kod och datalagret. Du kan få en detaljerad jämförelse av dessa tekniker i guiden GraphQL vs. RESTful API.

För att skapa en GraphQL-tjänst så börjar du med att definiera schematyper och skapa fält som använder dessa typer. Därefter så tillhandahåller du en funktions-resolver som ska utföras på varje fält och typ när data begärs av klientsidan.

Terminologi för GraphQL

GraphQL-typsystemet används för att beskriva vilka data som kan frågas ut och vilka data som du kan manipulera. Det är kärnan i GraphQL. Låt oss diskutera olika sätt att beskriva och manipulera data i GraphQL.

Objekttyper

GraphQL-objekttyper är datamodeller som innehåller starkt typade fält. Det bör finnas en 1 till 1-mappning mellan dina modeller och GraphQL-typerna. Nedan finns ett exempel på en GraphQL-typ:

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

Sökfrågor

GraphQL Query definierar alla sökfrågor som en klient kan köra på GraphQL API. Du bör definiera en RootQuery som inkluderar alla befintliga sökfrågor enligt konventionen.

Nedan definierar och mappar vi frågorna till motsvarande 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
}

Mutationer

Om GraphQL-förfrågningar är GET -förfrågningar så är mutationerna POST, PUT, PATCH och DELETE -förfrågningar som manipulerar GraphQL API.

Vi kommer att placera alla mutationer i en enda RootMutation för att demonstrera detta:

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 noterat användningen av -input-typer för mutationerna, t.ex. UserInput, TodoInput. Det är alltid bäst att alltid definiera Input-typer för att skapa och uppdatera dina resurser.

Du kan definiera inmatningstyperna på följande sätt:

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

Resolvers

Resolvers talar om för GraphQL vad de ska göra när varje sökfråga eller mutation begärs. Det är en grundläggande funktion som gör det hårda arbetet med att slå på databaslagret för att göra CRUD-operationer (skapa, läsa, uppdatera, ta bort), slå på en intern RESTful API-slutpunkt eller anropa en mikrotjänst för att uppfylla klientens begäran.

Du kan skapa en ny resolvers.js-fil och lägga till följande kod:

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 är det som GraphQL exponerar för världen. Av den anledningen så kommer typer, sökfrågor och mutationer att ingå i schemat för att exponeras för världen.

Nedan beskrivs hur man exponerar typer, sökfrågor och mutationer för världen:

schema {
  query: RootQuery
  mutation: RootMutation
}

I ovanstående skript så har vi inkluderat RootQuery och RootMutation som vi skapade tidigare för att de ska exponeras för omvärlden.

Hur fungerar GraphQL med Node.js och Express.js?

GraphQL tillhandahåller en implementering för alla större programmeringsspråk, och Node.js är inte undantaget. På den officiella GraphQL-webbplatsen så finns det en sektion för JavaScript-stöd, och det finns även andra implementeringar av GraphQL för att göra det enkelt att skriva och koda.

GraphQL Apollo tillhandahåller en implementering för Node.js och Express.js och gör det enkelt att komma igång med GraphQL.

Inästa avsnitt så kommer du att lära dig hur du skapar och utvecklar din första GraphQL-applikation i Node.js och Express.js backend-ramverk med hjälp av GraphQL Apollo.

Konfigurera GraphQL med Express.js

Att bygga en GraphQL API-server med Express.js är enkelt. I det här avsnittet så kommer vi att utforska hur man bygger en GraphQL-server.

Initialisera projektet med Express

Först så måste du installera och ställa in ett nytt Express.js-projekt. Skapa en mapp för ditt projekt och installera Express.js med det här kommandot:

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

Kommandot ovan skapar en ny package.json-fil och installerar Express.js-biblioteket i ditt projekt.

Därefter så strukturerar vi vårt projekt enligt bilden nedan. Det kommer att innehålla olika moduler för projektets funktioner som t.ex. användare, todos osv.

Filer för graphql-todo.
Filer för graphql-todo.

Initialisera GraphQL

Låt oss börja med att installera GraphQL Express.js-beroendena. Kör följande kommando för att installera:

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

Skapa scheman och typer

Därefter så ska vi skapa en index.js-fil i mappen modules och lägga till följande kodutdrag:

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;

Kodgenomgång

Vi går igenom kodutdraget och bryter ner det:

Steg 1

Först så importerade vi de nödvändiga biblioteken och skapade standardtyper för sökfrågor och mutationer. Förfrågan och mutationen anger för tillfället bara versionen av GraphQL API :et. Vi kommer dock att utöka förfrågan och mutationen för att inkludera andra scheman allteftersom vi fortsätter.

Import av GraphQL och tillägg.
Import av GraphQL och tillägg.
Steg 2:

Därefter så skapade vi en ny skalär typ för tid och vår första resolver för den sökfråga och mutation som skapats ovan. Vi genererade dessutom även ett schema med hjälp av funktionen makeExecutableSchema.

Det genererade schemat inkluderar alla andra scheman som vi importerade och kommer även att innehålla fler när vi skapar och importerar dem.

Skapa en skalär typ för tid samt vår första resolver.
Skapa en skalär typ för tid samt vår första resolver.

Ovanstående kodutdrag visar att vi importerade olika scheman till funktionen makeExecutableSchema. Detta tillvägagångssätt hjälper oss att strukturera applikationen för komplexitet. Därefter så ska vi skapa de Todo- och User-scheman som vi importerade.

Skapa Todo-schema

Todo-schemat visar enkla CRUD-operationer som applikationens användare kan utföra. Nedan visas schemat som implementerar 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 };

Genomgång av koden

Låt oss gå igenom kodutdraget och bryta ner det:

Steg 1:

Först så skapade vi ett schema för vår Todo med hjälp av GraphQL type, input och extend. Nyckelordet extend används för att ärva och lägga till nya sökfrågor och mutationer till den befintliga rotfrågan och mutationen som vi skapade ovan.

Skapa schemat för vår Todo.
Skapa schemat för vår Todo.
Steg 2:

Därefter så skapade vi en resolver, som används för att hämta rätt data när en viss sökfråga eller mutation anropas.

Skapa en resolver.
Skapa en resolver.

Med resolver-funktionen på plats så kan vi skapa enskilda metoder för affärslogiken och databasmanipulationen som visas i exemplet create-todo.js.

Skapa filen create-user.js i mappen ./mutations/ och lägg till affärslogiken för att skapa en ny Todo i din databas.

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

Kodutdraget ovan är ett förenklat sätt att skapa en ny Todo i vår databas med hjälp av Sequelize ORM. Du kan läsa mer om Sequelize och hur du konfigurerar det med Node.js.

Du kan följa samma steg för att skapa många scheman beroende på din applikation eller så kan du klona hela projektet från GitHub.

Därefter så ska vi konfigurera servern med Express.js och köra den nyskapade Todo-applikationen med GraphQL och Node.js.

Konfigurera och köra servern

Slutligen så ska vi konfigurera vår server med hjälp av biblioteket apollo-server-express som vi installerade tidigare.

apollo-server-express är en enkel wrapper av Apollo Server för Express.js, Den rekommenderas eftersom den har utvecklats för att passa i Express.js-utveckling.

Med hjälp av de exempel som vi diskuterade ovan så ska vi konfigurera Express.js-servern så att den fungerar med det nyinstallerade apollo-server-express.

Skapa en server.js-fil i rotkatalogen och klistra in följande kod:


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 koden ovan så har du framgångsrikt skapat din första CRUD GraphQL-server för Todos och Users. Du kan starta din utvecklingsserver och få tillgång till denna med hjälp av http://localhost:3000/graphql. Om allt har lyckats så bör du se skärmen nedan:

Verifieringsskärmen.
Verifieringsskärmen.

Sammanfattning

GraphQL är en modern teknik som stöds av Facebook och som förenklar det tråkiga arbetet med att skapa storskaliga API: er med RESTful-arkitekturmönster.

Den här guiden har förklarat GraphQL och visat hur man utvecklar sitt första GraphQL API med Express.js.

Låt oss veta vad du bygger med hjälp av GraphQL i kommentarerna nedan.

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