GraphQL API
All about GraphQL
GraphQL is an modern API technology that gives client more control over the data they receive. Also known as a query language for APIs. It lets client apps ask the server for the exact data and shape they need.
Good to know
One of the biggest reason to transition into graphQL is when dealing with different clients such as: mobile, desktop, etc.
Instead of calling many REST endpoints and combining responses together, a GraphQL client sends one structured query and receives a predictable response.
Facebook created GraphQL to fix the most common REST API issues: underfetching and overfetching.
-
Underfetching
Traditional REST API often calls multiple endpoints. For example, we may have different endpoint calls for: users, posts, commments, replies, reactions, and more. Each endpoint returns one resource. That pattern is called underfetching. The server responds a single resource for each request, forcing the client to make several round trips.
Instead of chasing each resource through many endpoints, GraphQL makes a single query request to a single GraphQL endpoint. The server responds with the exact data shape the client asked for.
-
Overfetching
In REST API, say the client only needs username field from user. Client still has to call the
/usersendpoint which returns the entire user object with fields the UI will never read. This is overfetching, the client receives way more data than needed.GraphQL fixes this by making fields explicit. The client lists all fields it wants in the GraphQL query. The server returns only those fields, nothing more. This gives the client full control over the shape of the data, eliminating overfetching.
Building GraphQL APIs
GraphQL has five ideas you need to understand before touching code.
- Schema: the contract between client and server.
- Types: the shape of your data.
- Queries: read data.
- Mutations: write data.
- Resolvers: functions that fetch or change data.
Install server dependencies
npm install express graphql express-graphqlProject structure
server/
├─ src/
│ ├─ db/
│ │ ├─ schema.ts
│ │ └─ client.ts
│ └─ graphql/
│ ├─ schema.ts
│ └─ resolvers.ts
└─ index.tsServer
GraphQL schema defines what clients can ask for. Treat this as the public contract.
import {
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
} from 'graphql';
import { users, posts } from '../db/schema';
import { db } from '../db/client';
import { eq } from 'drizzle-orm';
const PostType = new GraphQLObjectType({
name: 'Post',
fields: {
id: { type: GraphQLInt },
title: { type: GraphQLString },
},
});
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString },
posts: {
type: new GraphQLList(PostType),
resolve: async (user) => {
return db.select().from(posts).where(eq(posts.userId, user.id));
},
},
},
});
const RootQuery = new GraphQLObjectType({
name: 'Query',
fields: {
users: {
type: new GraphQLList(UserType),
resolve: async () => {
return db.select().from(users);
},
},
},
});
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
createUser: {
type: UserType,
args: {
name: { type: new GraphQLNonNull(GraphQLString) },
},
resolve: async (_, { name }) => {
const [user] = await db
.insert(users)
.values({ name })
.returning();
return user;
},
},
},
});
export const schema = new GraphQLSchema({
query: RootQuery,
mutation: Mutation,
});Let's create a Express server
import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { schema } from './graphql/schema';
const app = express();
app.use(
'/graphql',
graphqlHTTP({
schema,
graphiql: true,
}),
);
app.listen(4000, () => {
console.log('GraphQL server running on http://localhost:4000/graphql');
});Client
Set up client with React
npm install @apollo/client graphqlTo use GraphQL in the client, we need to set up Apollo.
import { ApolloClient, InMemoryCache } from '@apollo/client';
export const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache(),
});Then create a context Apollo provider
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ApolloProvider } from '@apollo/client';
import { client } from './apollo';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
);Query
Here we set all the queries from the client.
import { gql } from '@apollo/client';
export const GET_USERS = gql`
query GetUsers {
users {
id
name
posts {
id
title
}
}
}
`;Note the shape of the data is nested: users and posts.
In your React component:
import { useQuery } from '@apollo/client';
import { GET_USERS } from './queries/users';
export default function App() {
const { data, loading } = useQuery(GET_USERS);
if (loading) return <p>Loading</p>;
return (
<ul>
{data.users.map((user) => (
<li key={user.id}>
{user.name}
<ul>
{user.posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</li>
))}
</ul>
);
}Mutation
import { gql, useMutation } from '@apollo/client';
export const CREATE_USER = gql`
mutation CreateUser($name: String!) {
createUser(name: $name) {
id
name
}
}
`;In your React app, simply use mutation in a form:
import { useMutation } from '@apollo/client';
import { CREATE_USER } from './mutations/users';
const [createUser] = useMutation(CREATE_USER);
createUser({ variables: { name: 'Ana' } });