Building Simple Product API with Apollo GraphQL and PostgreSQL
Introduction
GraphQL has transformed how we build APIs by offering a flexible and powerful alternative to REST. It enables clients to request exactly the data they need, making it a great fit for modern applications. In this article, we'll explore GraphQL, its use cases, and key concepts like queries, mutations, and resolvers. We'll also walk you through building a simple product API using Apollo GraphQL with RapidApp PostgreSQL as your datasource.
What is GraphQL?
GraphQL is a query language for your API that allows clients to request specific data, avoiding over-fetching or under-fetching. Unlike REST, where multiple endpoints might be needed, GraphQL provides a single endpoint through which clients can access multiple data sources and types with one request.
Use Cases
GraphQL is widely used for:
- Applications with complex data relationships
- APIs that need to return different data to different clients
- Projects aiming to optimize network usage
- Microservices architecture integration
For example, you can use GraphQL to retrieve only the required fields from a product database, improving efficiency in e-commerce applications.
What Are Queries, Mutations, and Resolvers?
- Query: Used to read or fetch data. In our case, we will create a query to retrieve products.
- Mutation: Used to create, update, or delete data. We will create mutations to add new products to our datasource.
- Resolvers: Functions that handle the execution of a query or mutation and interact with the datasource, such as a PostgreSQL database.
Persistence Layer
In this article, we will be using PostgreSQL as our database. You can maintain your database in any database management system. For a convenient deployment option, consider cloud-based solutions like Rapidapp, which offers managed PostgreSQL databases, simplifying setup and maintenance.
Create a free database in Rapidapp in seconds here
Step-by-Step Implementation
Project Initialization
We will use Node.js and it already has a package manager called npm. Let's start by creating a new Node.js project:
mkdir pg-graphql
cd pg-graphql
npm init -y
Installing Dependencies
We need to install the following packages:
apollo-server
: A GraphQL server library for Node.js.graphql
: The JavaScript reference implementation for GraphQL.pg
: A PostgreSQL client for Node.js.datasource-sql
: A SQL datasource for Apollo Server.
npm install apollo-server graphql pg datasource-sql
Creating Schema
Create a new file named schema.js
in the root directory of your project and define the schema for your product API:
const { gql } = require('apollo-server');
const typeDefs = gql`
type Product {
id: ID!
name: String!
price: Float!
description: String
}
type Query {
products: [Product]
product(id: ID!): Product
}
type Mutation {
createProduct(name: String!, price: Float!, description: String): Product
updateProduct(id: ID!, name: String, price: Float, description: String): Product
}
`;
module.exports = typeDefs;
Line 4: Defines the Product
type with fields id
, name
, price
, and description
.
Line 11: Defines the Query
type with two queries: products
to fetch all products and product
to fetch a single product by ID.
Line 16: Defines the Mutation
type with two mutations: createProduct
to add a new product and updateProduct
to update an existing product.
This is kind of a contract of our API, and we will be using this in the server definition soon.
Implementing Product API
Create a new file named product_api.js
in the root directory of your project and implement the product API:
const { SQLDataSource } = require('datasource-sql');
class ProductAPI extends SQLDataSource {
getProducts() {
return this.knex.select('*').from('products');
}
getProductById(id) {
return this.knex('products').where({ id }).first();
}
updateProduct(id, product) {
return this.knex('products')
.where({ id })
.update(product)
.returning('*')
.then(rows => rows[0]);
}
createProduct({ name, price, description }) {
return this.knex('products')
.insert({ name, price, description })
.returning('*');
}
}
module.exports = ProductAPI;
Knex is a SQL query builder for Node.js that we will use to interact with our PostgreSQL database.
Line 5: Retrieves all products from the products
table.
Line 9: Retrieves a single product by ID.
Line 13: Updates an existing product by ID.
Line 21: Adds a new product to the products
table.
Now that we have our API, let's use it in resolvers to handle queries and mutations.
Implementing Resolvers
Create a new file named resolvers.js
in the root directory of your project and implement the resolvers:
const resolvers = {
Query: {
products: async (_, __, { dataSources }) => {
return dataSources.productAPI.getProducts();
},
product: async (_, { id }, { dataSources }) => {
return dataSources.productAPI.getProductById(id);
},
},
Mutation: {
createProduct: async (_, { name, price, description }, { dataSources }) => {
return dataSources.productAPI.createProduct({ name, price, description });
},
updateProduct: async (_, { id, name, price, description }, { dataSources }) => {
const updatedProduct = { name, price, description };
return dataSources.productAPI.updateProduct(id, updatedProduct);
},
},
};
module.exports = resolvers;
Resolvers are functions that execute queries and mutations. They interact with the datasource to fetch or update data. In our case, it uses our Product API for better abstraction. With the help of resolvers, queries and mutations will be available in our GraphQL API.
Setting Up Apollo Server
Create a new file named server.js
in the root directory of your project and set up the Apollo Server:
const { ApolloServer } = require('apollo-server');
const typeDefs = require('./schema');
const resolvers = require('./resolvers');
const ProductAPI = require('./product_api');
// PostgreSQL connection setup
const knexConfig = {
client: 'pg',
connection: {
host: '<host>',
user: '<user>',
password: '<password>',
database: '<database>',
ssl: {
rejectUnauthorized: false
},
application_name: 'apollo'
},
pool: { min: 0, max: 3 },
};
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => ({
productAPI: new ProductAPI(knexConfig),
}),
introspection: true,
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
Line 22: ApolloServer is initialized with the schema, resolvers, and data sources. With the instrospection
option set to true
,
you can explore the schema using tools like GraphQL Playground.
Running Application
Run the following command to start your Apollo Server:
node server.js
Testing the API
Once the server is ready, navigate to http://localhost:4000
in your browser to access the GraphQL Playground. You will
see a button to go to the Apollo Studio page where you can explore your schema, run queries, and test mutations.
Creating a New Product
To create a new product, run the following mutation in the Playground:
mutation {
createProduct(name: "New Product", price: 99.99, description: "description") {
id
name
price
description
}
}
Fetching All Products
To fetch all products, run the following query:
query {
products {
id
name
price
description
}
}
Getting a Single Product
query {
product(id: 1) {
id
name
price
description
}
}
Updating a Product
mutation {
updateProduct(id: 1, name: "Updated Product", price: 199.99, description: "New Description") {
id
name
price
description
}
}
Conclusion
In this article, we covered how to set up a simple product API using Apollo GraphQL and PostgreSQL as a service. GraphQL’s flexibility with queries and mutations, combined with a powerful PostgreSQL datasource, makes it a great solution for building efficient APIs. If you're looking for a managed PostgreSQL solution, consider using RapidApp PostgreSQL to get started quickly.
You can find the complete source code for this project on GitHub.