GraphQL APIs: Alternative of REST
GraphQL is a query language for APIs and a runtime for executing those queries against your data. Developed by Facebook in 2012 and released publicly in 2015, GraphQL solves many common issues with REST APIs:
Precise data fetching: Request exactly what you need, nothing more, nothing less
Single request: Fetch data from multiple resources in a single API call
Strong type system: Schema-defined API with clear contracts
Introspection: APIs can be queried for their own capabilities.
Why Use Apollo?
Apollo Server makes building GraphQL APIs very straightforward: you start by writing your schema in GraphQL’s SDL (Schema Definition Language) and then “plug in” simple resolver functions to fetch your data. It offers easy, one‑line setup for frameworks like Express, Koa, Fastify, or AWS Lambda, and includes performance boosters such as query‑plan and response caching plus built‑in tracing that you can view in Apollo Studio. If you’re running multiple GraphQL services, its Federation feature lets you stitch them into a single unified API, and its developer tools—GraphQL Playground (Apollo Sandbox), automatic schema mocking, and support for persisted queries—help you test, prototype, and deploy faster.
In this blog, we are using Apollo server and client for implementing GraphQL.
Implement GraphQL in Node.js (Server side)
Apollo Server is a popular GraphQL server implementation for Node.js. Let’s build a simple server:
Javascript
const { ApolloServer } = require('@apollo/server');
const { startStandaloneServer } = require('@apollo/server/standalone');
// Define your GraphQL schema
const typeDefs = `
type Book {
id: ID!
title: String!
author: Author!
publishedYear: Int
}
type Author {
id: ID!
name: String!
books: [Book!]!
}
type Query {
books: [Book!]!
book(id: ID!): Book
authors: [Author!]!
author(id: ID!): Author
}
type Mutation {
addBook(title: String!, authorId: ID!, publishedYear: Int): Book!
}
`;
// Sample data
const books = [
{ id: '1', title: 'The Awakening', authorId: '1', publishedYear: 2020 },
{ id: '2', title: 'City of Glass', authorId: '2', publishedYear: 2018 },
];
const authors = [
{ id: '1', name: 'Kate Chopin' },
{ id: '2', name: 'Paul Auster' },
];
// Define resolvers
const resolvers = {
Query: {
books: () => books,
book: (_, { id }) => books.find(book => book.id === id),
authors: () => authors,
author: (_, { id }) => authors.find(author => author.id === id),
},
Mutation: {
addBook: (_, { title, authorId, publishedYear }) => {
const newBook = {
id: String(books.length + 1),
title,
authorId,
publishedYear,
};
books.push(newBook);
return newBook;
},
},
Book: {
author: (book) => authors.find(author => author.id === book.authorId),
},
Author: {
books: (author) => books.filter(book => book.authorId === author.id),
},
};
// Create Apollo Server instance
const server = new ApolloServer({
typeDefs,
resolvers,
});
// Start the server
async function startServer() {
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});
console.log(`🚀 Server ready at ${url}`);
}
startServer();
Key Components of the Apollo Server Implementation
- Type Definitions: Define your schema using GraphQL’s Schema Definition Language (SDL)
- Resolvers: JavaScript functions that return data for each field in your schema
- Relationships: Notice how we linked Books and Authors through resolver functions
- Queries and Mutations: Standard operations for reading and writing data
Implementing Apollo Client in React
Now let’s build a React application that consumes our GraphQL API:
javascript
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
// Initialize Apollo Client
const client = new ApolloClient({
uri: 'http://localhost:4000',
cache: new InMemoryCache(),
});
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
// src/App.js
import React from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';
import './App.css';
// Define GraphQL queries and mutations
const GET_BOOKS = gql`
query GetBooks {
books {
id
title
publishedYear
author {
name
}
}
}
`;
const ADD_BOOK = gql`
mutation AddBook($title: String!, $authorId: ID!, $publishedYear: Int) {
addBook(title: $title, authorId: $authorId, publishedYear: $publishedYear) {
id
title
publishedYear
}
}
`;
// Book list component
function BookList() {
const { loading, error, data } = useQuery(GET_BOOKS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div className="book-list">
<h2>Books</h2>
<ul>
{data.books.map(book => (
<li key={book.id}>
<h3>{book.title}</h3>
<p>By: {book.author.name}</p>
<p>Published: {book.publishedYear}</p>
</li>
))}
</ul>
</div>
);
}
// Add book form component
function AddBookForm() {
const [title, setTitle] = React.useState('');
const [authorId, setAuthorId] = React.useState('1');
const [publishedYear, setPublishedYear] = React.useState(2023);
const [addBook, { loading }] = useMutation(ADD_BOOK, {
refetchQueries: [{ query: GET_BOOKS }],
});
const handleSubmit = (e) => {
e.preventDefault();
addBook({
variables: {
title,
authorId,
publishedYear: parseInt(publishedYear)
}
});
setTitle('');
};
return (
<div className="add-book-form">
<h2>Add New Book</h2>
<form onSubmit={handleSubmit}>
<div>
<label>
Title:
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)} required />
</label>
</div>
<div>
<label>
Author:
<select value={authorId} onChange={(e) => setAuthorId(e.target.value)}>
<option value="1">Kate Chopin</option>
<option value="2">Paul Auster</option>
</select>
</label>
</div>
<div>
<label>
Published Year:
<input
type="number"
value={publishedYear}
onChange={(e) => setPublishedYear(e.target.value)}
/>
</label>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Adding...' : 'Add Book'}
</button>
</form>
</div>
);
}
function App() {
return (
<div className="App">
<h1>GraphQL Book Library</h1>
<div className="app-container">
<BookList />
<AddBookForm />
</div>
</div>
);
}
export default App;
Key Benefits of Apollo Client
- Declarative Data Fetching: Define your data requirements within your components
- Automatic Caching: Apollo Client’s normalized cache prevents duplicate requests
- Error Handling: Built-in loading and error states simplify UI management
- Optimistic UI: Update the UI immediately before server confirmation
- Refetching Queries: Easily refresh data after mutations
Why GraphQL + Apollo?
Development Experience
Apollo abstracts away much of the complexity of working with GraphQL. The development experience is significantly improved with:
- Strongly typed: Auto-generated TypeScript types from your schema
- Developer tools: Apollo DevTools for Chrome and Firefox
- Testing utilities: Mock your GraphQL operations for testing
- Incremental adoption: Can be used alongside REST APIs
Performance Optimization
GraphQL excels at minimizing network requests and optimizing data transfer:
- No over-fetching: Only request the data you need
- No under-fetching: Avoid multiple round-trips to the server
- Batched queries: Combine multiple queries into a single request
Best Practices
When working with GraphQL and Apollo:
- Design your schema for the frontend: Focus on how data will be used
- Use fragments for reusable field selections: Keep queries DRY
- Implement proper error handling: Surface errors meaningfully to users
- Consider pagination for large collections: Prevent performance issues
- Secure your API: Implement proper authentication and authorization