GraphQL × TypeScript × Zod: Type-safe API Development and Schema Management Using Code Generator

  • typescript
    typescript
  • apollo
    apollo
  • graphql
    graphql
  • github
    github
  • swagger
    swagger
Published on 2024/02/12

Introduction

In development using GraphQL, managing schemas and types is extremely important. In particular, unifying types between the backend and frontend and properly managing API changes leads to improved development efficiency and fewer bugs. This article introduces best practices for type-safe API development by combining technologies such as GraphQL Code Generator, TypeScript, Zod, and GraphQL SDL. It explains, in a unified way, how to establish a schema-driven development flow, manage schemas, validate responses, automatically generate API documentation, and automate API testing.

This article provides concrete solutions for those who have introduced GraphQL but are facing issues such as “schema change management is difficult,” “type mismatches occur on the frontend,” or “I don’t know how to validate responses.”

Introducing GraphQL Code Generator and Automatically Generating Type Definitions

What is GraphQL Code Generator?

GraphQL Code Generator is a tool that automatically generates TypeScript types and code from GraphQL schemas. This prevents type mismatches between the frontend and backend and improves development efficiency.

https://the-guild.dev/graphql/codegen

Benefits

The main purpose of using GraphQL Code Generator is “to leverage type definitions automatically generated from the schema and efficiently implement the frontend and backend.”

  • When you change the schema, the type definitions are automatically updated
  • You can enhance type safety by integrating with TypeScript
  • Implementations on both the frontend and backend always stay in sync with the schema

Especially in team development or large-scale projects, introducing automatic type generation provides significant benefits in both work efficiency and bug reduction.

Installation and Basic Setup

Install @graphql-codegen/cli with the following command:

npm install --save-dev @graphql-codegen/cli

Next, add the necessary plugins. To generate TypeScript type definitions, install the following plugins:

npm install --save-dev @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo

First, define the GraphQL schema in schema.graphql.

schema.graphql
type Author {
  id: Int!
  firstName: String!
  lastName: String!
  posts(findTitle: String): [Post]
}
 
type Post {
  id: Int!
  title: String!
  author: Author
}
 
type Query {
  posts: [Post]
}

Key point

  • This is the server’s schema definition, and type definitions are generated based on this information.

If you were not using GraphQL Code Generator and wanted to access GraphQL on the client side, you might write something like the following:

import { request, gql } from 'graphql-request'
import { useQuery } from '@tanstack/react-query'
 
interface PostsQuery {
  posts: {
    id: string
    title: string
    author?: {
      id: string
      firstName: string
      lastName: string
    }
  }[]
}
 
const postsQueryDocument = gql`
  query Posts {
    posts {
      id
      title
      author {
        id
        firstName
        lastName
      }
    }
  }
`
 
const Posts = () => {
  const { data } = useQuery<PostsQuery>('posts', async () => {
    const { posts } = await request(endpoint, postsQueryDocument)
    return posts
  })
 
}
interface PostsQuery {
  posts: {
    id: string
    title: string
    author?: {
      id: string
      firstName: string
      lastName: string
    }
  }[]
}

In this way, you need to prepare the response type for useQuery in advance before fetching data.

The role of GraphQL Code Generator is to eliminate the need to write this type definition manually.

Create a configuration file codegen.ts and specify the schema and target GraphQL operations.

import type { CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
   schema: 'https://localhost:4000/graphql',
   documents: ['src/**/*.tsx'],
   generates: {
      './src/gql/': {
        preset: 'client',
      }
   }
}
export default config

Next, run the following command to automatically generate type definitions:

npx graphql-codegen

Then you can fetch data as shown below:

import { request } from 'graphql-request'
import { useQuery } from '@tanstack/react-query'
import { graphql } from './gql/gql'
 
const postsQueryDocument = graphql(/* GraphQL */ `
  query Posts {
    posts {
      id
      title
      author {
        id
        firstName
        lastName
      }
    }
  }
`)
 
const Posts = () => {
  const { data } = useQuery<PostsQuery>('posts', async () => {
    const { posts } = await request(endpoint, postsQueryDocument)
    return posts
  })
}

In this way, you can fetch data using types automatically generated from the schema definition without having to create response types yourself.

If you were separately creating both the GraphQL schema definition and the response (TypeScript) type definitions, you would need to modify both whenever the definitions change.

There is also a risk that a developer who is not fully aware of the situation might update only one of the two definitions, causing inconsistencies and potentially leading to data-fetching failures.

From this perspective as well, it is desirable to manage them centrally.

Schema-Driven Development (Establishing a GraphQL SDL-Based Development Flow)

What is Schema-Driven Development (SDD)?

Schema-Driven Development (SDD) is a development approach in which API specifications are designed and implemented based on GraphQL schema definitions (SDL: Schema Definition Language).

With traditional REST APIs, you needed to create a separate API specification document and share it between the frontend and backend. However, in GraphQL, the schema itself becomes the API specification, which makes the development process smoother.

Benefits

  • Clarified specifications
    Since the schema itself is the API specification, discrepancies in understanding of the spec between frontend and backend are less likely to occur.
  • Automatic documentation
    By using tools such as GraphQL Playground or Apollo Studio, you can automatically generate documentation from schema definitions, making it easier for API consumers to understand.
  • Easy detection of changes
    When there are changes to the schema, consistency checks with the implementation are more likely to occur automatically, making the impact of changes clearer.

By following this step, you can improve the overall team’s work efficiency and achieve smooth communication and project management.

Development Flow

Create Schema Definitions

type Author {
  id: Int!
  firstName: String!
  lastName: String!
}
 
type Post {
  id: Int!
  title: String!
  author: Author
}
 
type Query {
  posts: [Post] # Retrieve multiple posts
  post(id: ID!): Post # Retrieve a single post
}

This schema functions as the base of the API and becomes the shared understanding between the frontend and backend.

4. Backend Implementation

Implement backend resolvers using TypeScript types generated from the schema.

Benefits

  • Since the schema becomes the API specification, the development direction does not drift
  • You can ensure that the implementation conforms to the schema

Resolver definition

resolvers.ts
// Dummy data
const posts = [
  { id: 1, title: "GraphQL Introduction", authorId: 1 },
  { id: 2, title: "Advanced GraphQL", authorId: 1 },
  { id: 3, title: "GraphQL with Apollo", authorId: 2 },
];

export const resolvers = {
  Query: {
    // Retrieve all posts
    posts: () => posts,

    // Retrieve a specific post by ID
    post: (_parent: any, args: { id: number }) =>
      posts.find((post) => post.id === args.id),
  },
};

Parse the GraphQL schema definition as gql (GraphQL tag).

type.ts
import { readFileSync } from "fs";
import { gql } from "graphql-tag";
import path from "path";

const schemaPath = path.resolve(process.cwd(), "../packages/graphql/schema/post.graphql");

const typeDefs = gql(readFileSync(schemaPath, "utf-8"));

export { typeDefs };

Finally, integrate the GraphQL server into Express and start it.

index.ts
import express from "express";
import cors from "cors";
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { resolvers } from './graphql/resolvers';
import { typeDefs } from "./graphql/type";

const server = new ApolloServer({ typeDefs, resolvers });

const app = express();
const PORT = process.env.PORT || 4000;

const startServer = async () => {
  await server.start();

  app.use(
    '/graphql',
    cors(),
    express.json(),
    expressMiddleware(server)
  );

  app.listen(PORT, () => {
    console.log('GraphQL server is running at http://localhost:4000/graphql');
  });
};

startServer();

Automatically Generate TypeScript Types from the Schema

By using GraphQL Code Generator to automatically generate TypeScript types from the GraphQL schema, you can achieve type-safe development.

Benefits

  • When the API changes, type errors occur, preventing missed fixes
  • Client-side development becomes safer

Create a configuration file for GraphQL Code Generator (@graphql-codegen/cli) and configure it to automatically generate typed TypeScript code from the GraphQL schema and queries.

import { CodegenConfig } from '@graphql-codegen/cli';
 
const config: CodegenConfig = {
  schema: '../packages/graphql/schema/**/*.graphql',
  generates: {
    "generated/graphql.ts": {
      plugins: [
        'typescript',
        'typescript-operations',
      ],
    },
  } 
};
export default config;

TypeScript types are generated.

export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: { input: string; output: string; }
  String: { input: string; output: string; }
  Boolean: { input: boolean; output: boolean; }
  Int: { input: number; output: number; }
  Float: { input: number; output: number; }
};

export type Author = {
  __typename?: 'Author';
  firstName: Scalars['String']['output'];
  id: Scalars['Int']['output'];
  lastName: Scalars['String']['output'];
  posts?: Maybe<Array<Maybe<Post>>>;
};


export type AuthorPostsArgs = {
  findTitle?: InputMaybe<Scalars['String']['input']>;
};

export type Post = {
  __typename?: 'Post';
  author?: Maybe<Author>;
  id: Scalars['Int']['output'];
  title: Scalars['String']['output'];
};

export type Query = {
  __typename?: 'Query';
  post?: Maybe<Post>;
  posts?: Maybe<Array<Maybe<Post>>>;
};


export type QueryPostArgs = {
  id: Scalars['ID']['input'];
};

Frontend Implementation

Implement GraphQL queries on the frontend using TypeScript types generated from the schema.

Benefits

  • Prevents discrepancies between API specifications and implementation
  • Since schema changes are reflected in types, they are easily detected as type errors

First, create a GraphQL query.

query GetPosts {
  posts {
    id
    title
    author {
      firstName
      lastName
    }
  }
}

Change the Code Generator config settings.

apps/frontend/config/config.ts
import { CodegenConfig } from '@graphql-codegen/cli';
 
const config: CodegenConfig = {
  schema: '../packages/graphql/schema/**/*.graphql',
  documents: '../packages/graphql/operations/**/*.graphql',
  generates: {
    "generated/graphql.ts": {
      plugins: [
        'typescript',
        'typescript-operations',
        'typescript-graphql-request',
      ],
    },
  } 
};
export default config;

Then new definitions and return types related to the query are generated and can be used on the frontend.

export type GetPostsQuery = { 
  __typename?: 'Query', 
  posts?: Array<{
     __typename?: 'Post', 
     id: number, 
     title: string, 
     author?: { 
      __typename?: 'Author', 
      firstName: string, 
      lastName: string 
    } | null 
  } | null> | null 
};

export const GetPostsDocument = gql`
    query GetPosts {
  posts {
    id
    title
    author {
      firstName
      lastName
    }
  }
}
    `;

export type SdkFunctionWrapper = <T>(action: (requestHeaders?:Record<string, string>) => Promise<T>, operationName: string, operationType?: string, variables?: any) => Promise<T>;


const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType, _variables) => action();

export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) {
  return {
    GetPosts(variables?: GetPostsQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<GetPostsQuery> {
      return withWrapper((wrappedRequestHeaders) => client.request<GetPostsQuery>(GetPostsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'GetPosts', 'query', variables);
    }
  };
}
export type Sdk = ReturnType<typeof getSdk>;
service.ts
import { GraphQLClient } from "graphql-request";
import { getSdk } from "../generated/graphql"; 

const client = new GraphQLClient("http://localhost:4000/graphql");
const sdk = getSdk(client);

export async function getPost() {
  const response = await sdk.GetPosts();
  return response.posts
}
import { useEffect, useState } from "react";
import { GetPostsQuery } from "../generated/graphql";
import { getPosts } from "./service";

function App() {
  const [posts, setPosts] = useState<GetPostsQuery["posts"] | null>(null);

  useEffect(() => {
    getPosts()
      .then((data) => {
        setPosts(data);
      })
      .catch((error) => {
        console.error(error);
      });
  }, []);

  return (
    <div>
      <h1>React + Express</h1>
      {posts === null || posts === undefined ? (
        <p>Loading...</p>
      ) : posts.length === 0 ? (
        <p>No posts found.</p>
      ) : (
        <ul>
          {posts.map((post) => (
            <li key={post?.id}>
              <h2>{post?.title}</h2>
              <p>Author: {post?.author?.firstName} {post?.author?.lastName}</p>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default App;

GraphQL Schema Management (Tracking Change History and Versioning)

A GraphQL schema is like a contract that defines communication between the frontend and backend. Therefore, when the schema changes, the consistency between the two can be broken.

In particular, when there are breaking changes, there is a risk that client-side code will stop working, so schema versioning and tracking change history are important.

Versioning Schemas Using Git

By managing schema files in a Git repository and tracking change history, you can see when and how changes were made.

Also, on GitHub, you can use GitHub Actions to automatically add labels to PRs when there are changes to schema.graphql.

1. Create .github/labeler.yml (Configuration to Detect Schema Changes)

.github/labeler.yml
schema-change:
  - "apps/packages/graphql/schema/**/*.graphql"
  • Detects only changes to schema files.

2. Create a GitHub Actions Workflow (Add Labels to PRs) .github/workflows/label-schema-changes.yml

.github/labeler.yml
name: "Add label for GraphQL schema changes"

on:
  pull_request:

permissions:
  pull-requests: write
  contents: read

jobs:
  label:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Apply labels
        uses: actions/labeler@v4
        with:
          repo-token: "${{ secrets.GITHUB_TOKEN }}"
  • When there are changes under apps/packages/graphql/schema/**/*.graphql, the label schema-change is automatically added to the PR.

Image from Gyazo

When you create a PR:

  • ① The GitHub Actions workflow that adds labels is executed
  • schema-change is set in Labels.

In addition to notifying via labels, you can also send notifications by using Slack integration in GitHub Actions workflows.

Schema Migration

Instead of changing the GraphQL schema directly, you can minimize the impact on clients by performing a gradual migration.

Flow for safe schema changes

  1. Prioritize non-breaking (backward compatible) changes
    • Adding fields
    • Changing default values
  2. When breaking changes are necessary, use deprecation
    • Use the @deprecated directive to deprecate old fields
    • Remove them after the client-side changes are complete

Example:

type Post {
  id: Int!
  title: String!
  description: String @deprecated(reason: "Use 'excerpt' instead.")
  author: Author
}

By adding @deprecated like this, you can notify clients that “this field will be removed in the future.”

When creating queries in GraphQL Playground, it also shows that the field is deprecated, allowing you to inform developers.

Image from Gyazo

Backend Response Validation (Using Zod, etc.)

Backend response validation is the process of verifying that the data returned from the API to the client has the expected format and structure. Proper validation helps prevent errors on the frontend and strengthens security.

Why Response Validation Is Necessary

  1. Ensuring data consistency
    Because the backend may return unexpected data, validation is needed to prevent the frontend from malfunctioning.

  2. Reducing security risks
    If invalid data is included in the response, security risks such as XSS and data tampering can occur.

  3. Early bug detection
    If the response structure changes due to API changes, validation can immediately detect the problem.

How to Implement Response Validation

  1. Introduce schema validation
    It is common to define the response structure using JSON Schema or similar and perform validation based on it.

Example: Validation using Zod (Node.js)

import { z } from 'zod';

const userSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});

const validateResponse = (data: any) => {
  const result = userSchema.safeParse(data);
  if (!result.success) {
    console.error("Invalid response data:", result.error.format());
    throw new Error("Response validation failed");
  }
  return result.data;
};
  1. Implement validation on the backend
    When generating backend responses, check data types and values to avoid returning invalid data.

Example of response validation in Express

import express from 'express';
import { z } from 'zod';

const app = express();

const responseSchema = z.object({
  success: z.boolean(),
  data: z.object({
    id: z.number(),
    name: z.string(),
  }),
});

app.get('/user', (req, res) => {
  const responseData = {
    success: true,
    data: { id: 1, name: "Taro" },
  };

  const validationResult = responseSchema.safeParse(responseData);
  if (!validationResult.success) {
    return res.status(500).json({ error: "Invalid response format" });
  }

  res.json(responseData);
});

app.listen(3000, () => console.log("Server running on port 3000"));

Strengthening Type Checks When Fetching (Preventing Inconsistencies on the Frontend)

In frontend development, if the type of data obtained from the API is not as expected, the behavior of the application can become unstable. This article explains how to strengthen type checks when fetching and prevent inconsistencies on the frontend.

Type Definitions for API Responses

By using TypeScript to define the types of data returned from the API, you can develop in a type-safe manner.

interface User {
  id: number;
  name: string;
  email: string;
}

async function fetchUser(): Promise<User> {
  const response = await fetch("https://api.example.com/user");
  const data = await response.json();
  return data; // Add validation to strengthen type checking
}

Avoid Type Assertions

If you overuse type assertions (as Type), you may bypass compile-time errors even when inconsistent data is actually passed, preventing proper type checking from working.

async function fetchUser(): Promise<User> {
  const response = await fetch("https://api.example.com/user");
  const data = await response.json();
  return data as User; // ⚠️ With only type assertion, invalid data can still slip through
}

Type-Safe Data Checking with Zod

By using schema validation libraries such as Zod, you can strictly validate the structure of API responses.

import { z } from "zod";

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});

type User = z.infer<typeof UserSchema>;

async function fetchUser(): Promise<User> {
  const response = await fetch("https://api.example.com/user");
  const data = await response.json();
  return UserSchema.parse(data); // Perform type checking here
}

Manual Checks Using Type Guards

You can also manually perform type checks without using a validation library.

function isUser(data: unknown): data is User {
  return (
    typeof data === "object" && data !== null &&
    "id" in data && typeof (data as any).id === "number" &&
    "name" in data && typeof (data as any).name === "string" &&
    "email" in data && typeof (data as any).email === "string"
  );
}

async function fetchUser(): Promise<User> {
  const response = await fetch("https://api.example.com/user");
  const data = await response.json();
  if (!isUser(data)) {
    throw new Error("Invalid API response");
  }
  return data;
}

Type-Safe Development Using GraphQL Code Generator

import { gql } from "graphql-tag";
import { request } from "graphql-request";
import { UserQuery } from "@generated/graphql";

const GET_USER = gql`
  query getUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

async function fetchUser(id: string): Promise<UserQuery> {
  const response = await request("/graphql", GET_USER, { id });
  return response;
}

By applying GraphQL types in this way, you can guarantee the types of API responses.

Automatic API Documentation Generation

In API development, documentation maintenance and test automation are important elements. This section explains automatic API documentation generation using GraphQL Playground and Swagger, and API test automation using mock servers.

Using GraphQL Playground for API Documentation

In GraphQL, you can dynamically generate API documentation based on schema definitions. GraphQL Playground is a convenient tool that allows you to visually check API endpoints and instantly document schemas.

  1. Enable Playground with the following configuration:
import { ApolloServer, gql } from 'apollo-server';

const typeDefs = gql`
  type Query {
    hello: String
  }
`;

const resolvers = {
  Query: {
    hello: () => 'Hello, world!',
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`);
});
  1. When you start the server, you can access Playground at http://localhost:4000.

  2. You can immediately reflect changes to the GraphQL schema and automatically generate developer-facing API documentation.

Field information is displayed as shown below, and code completion is available when creating sample queries.

Image from Gyazo

REST API Documentation Using Swagger

For automatic REST API documentation generation, use Swagger (OpenAPI).

  1. Add swagger-jsdoc and swagger-ui-express to your Express-based API.
npm install swagger-jsdoc swagger-ui-express
  1. Add Swagger configuration.
import express from 'express';
import swaggerJsdoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';

const app = express();

const swaggerOptions = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'Sample API',
      version: '1.0.0',
    },
  },
  apis: ['./routes/*.js'],
};

const swaggerDocs = swaggerJsdoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs));

app.listen(3000, () => console.log('Server running on port 3000'));
  1. Access http://localhost:3000/api-docs to view the API documentation in Swagger UI.

API Test Automation (Using Mock Servers)

To efficiently test APIs, use mock servers. With tools like json-server and GraphQL’s @graphql-tools/mock, you can proceed with frontend development and testing even before backend development is complete.

Mock REST API Using JSON Server

json-server is a convenient tool that provides a REST API based on a simple JSON file. This allows you to proceed with frontend development and testing even if the backend implementation is not yet complete.

  1. Install json-server.

    npm install -g json-server
    
  2. Create db.json and prepare dummy data.

    {
      "users": [
        { "id": 1, "name": "Alice" },
        { "id": 2, "name": "Bob" }
      ]
    }
    
  3. Start json-server.

    json-server --watch db.json --port 3001
    
  4. Access http://localhost:3001/users to confirm that the mock API is running.

json-server also has the following customization features:

  • Custom route configuration: Create routes.json to customize endpoints.

    {
      "/api/users": "/users"
    }
    
  • This is for customizing json-server like Express. Normally, json-server is easy to use from the CLI, but by extending it like Express, you can handle requests more flexibly.

const jsonServer = require('json-server');
const server = jsonServer.create();  // Create an instance of JSON Server
const router = jsonServer.router('db.json');  // Generate routing based on db.json
const middlewares = jsonServer.defaults();  // Apply JSON Server default middleware (CORS, logging, JSON parsing, etc.)

server.use(middlewares);  // Apply middleware
server.use((req, res, next) => {
  console.log('Request received:', req.method, req.url);  // Output request logs
  next();  // Pass processing to the next middleware
});
server.use(router);  // Apply router

server.listen(3001, () => {
  console.log('JSON Server is running on port 3001');
});

Key points

  • By outputting all requests to the console, debugging becomes easier.
  • In addition to the standard json-server logs, you can control request details more precisely.
server.use('/users', (req, res, next) => {
  req.query.limit = 10;  // Forcefully add a query parameter
  next();
});

Key points

  • You can also modify request data when accessing /users.
router.render = (req, res) => {
  res.json({
    success: true,
    data: res.locals.data
  });
};
  • You can change the default json-server response to return a custom response.
  • You can add success: true to all responses.

Building a GraphQL Mock Server

  1. Use @graphql-tools/mock to build a GraphQL mock server.
npm install @graphql-tools/mock @graphql-tools/schema graphql-tag
  1. Add mock API configuration.
import { makeExecutableSchema } from '@graphql-tools/schema';
import { addMocksToSchema } from '@graphql-tools/mock';
import { graphql } from 'graphql';

const typeDefs = `
  type User {
    id: ID!
    name: String!
  }
  type Query {
    users: [User]
  }
`;

const schema = makeExecutableSchema({ typeDefs });
const mockedSchema = addMocksToSchema({ schema });

graphql(mockedSchema, '{ users { id name } }').then((result) =>
  console.log(JSON.stringify(result, null, 2))
);
  1. A mock API based on the schema is immediately generated and can be tested.

Conclusion

By using GraphQL Code Generator, you can streamline GraphQL schema management and automatic type definition generation, and prevent type mismatches between the backend and frontend. In addition, response validation with Zod and strengthened type checks when fetching enable safe data exchange. Furthermore, automatic API documentation generation and API test automation using mock servers can improve development efficiency.

By applying the methods introduced in this article, you can achieve type-safe API development that fully leverages the strengths of GraphQL. Try applying them to your projects to aim for a smoother development experience.

Xでシェア
Facebookでシェア
LinkedInでシェア

Questions about this article 📝

If you have any questions or feedback about the content, please feel free to contact us.
Go to inquiry form