Grafbase Resolvers are a powerful way to build your own GraphQL API. Developers can define a GraphQL schema and write GraphQL resolvers with TypeScript that are automatically deployed to the edge with caching and branching built-in.
Couchbase is an open-source, distributed NoSQL document-oriented database which focuses on performance, scalability, and ease of use. Unlike traditional relational databases, Couchbase stores data as JSON-like documents, offering greater flexibility in data modelling.
In this guide, we'll implement the following:
- GraphQL mutation to create a product
- GraphQL query to fetch all products
- Configure GraphQL Edge Caching
- Deploy to the edge with Grafbase
You should already have a Couchbase project up and running to follow along.
Here's a quick look at the local instance I have running:
You'll also need your username, password and query service API URL.
The Grafbase CLI emulates the production environment you get once you deploy with Grafbase so we'll be using that in development.
Begin by creating a new project using the command line inside of an existing project directory:
npx grafbase init -c typescript
You should now see the folder grafbase with the file grafbase.config.ts
. This file is where we will configure the final GraphQL schema that Grafbase will deploy to the edge.
We'll now begin by creating our first custom GraphQL mutation. We'll first need to declare the shape of the mutation, including any arguments and the return type.
Inside grafbase.config.ts
, replace the contents with:
import { config, graph } from '@grafbase/sdk'
const g = graph.Standalone()
export default config({
graph: g,
})
The GraphQL API we create with Grafbase is for products, so we'll need to define a GraphQL type for Product
:
const product = g.type('Product', {
id: g.id(),
name: g.string(),
slug: g.string(),
price: g.int(),
onSale: g.boolean().optional(),
})
Now we can extend the schema to add the GraphQL mutation productCreate
that:
- Accepts an
input
argument input type - Invoke the resolver file
create
- Returns a
Product
const productCreateInput = g.input('ProductCreateInput', {
name: g.string(),
slug: g.string(),
price: g.int(),
onSale: g.boolean().optional(),
})
g.mutation('productCreate', {
args: {
input: g.inputRef(productCreateInput),
},
resolver: 'products/create',
returns: g.ref(product).optional(),
})
That's all we need to do to configure the GraphQL mutation for productCreate
. The configuration above will generate a GraphQL schema (when we start the server) that looks something like this:
type Mutation {
productCreate(input: ProductCreateInput!): Product
}
type Product {
id: ID!
name: String!
slug: String!
price: Int!
onSale: Boolean
}
input ProductCreateInput {
name: String!
slug: String!
price: Int!
onSale: Boolean
}
Now create the file resolvers/create.ts
and add the following:
const username = process.env.COUCHBASE_USERNAME
const password = process.env.COUCHBASE_PASSWORD
const url = process.env.COUCHBASE_URL
export default async function ProductCreateResolver(_, { input }) {
// ... We'll do this next
}
You'll notice above we are assigning the following environment variables:
COUCHBASE_USERNAME
COUCHBASE_PASSWORD
COUCHBASE_URL
Make sure to add these to grafbase/.env
:
COUCHBASE_USERNAME=
COUCHBASE_PASSWORD=
COUCHBASE_URL=http://localhost:8093/query/service
Next, we'll see how easy it is to use edge-compatible NPM dependencies. Each Product
has an id
field but we don't have this field assigned on the input
type so we'll instead automatically generate an id
using the nanoid
package.
npm install nanoid
Now update resolvers/products/create.ts
to include the nanoid
import:
import { nanoid } from 'nanoid'
We'll then update the ProductCreateResolver
function to invoke nanoid()
and make a fetch request passing along the Basic Authorization
header with the Couchbase username and password:
export default async function ProductsCreateResolver(_, { input }) {
const id = nanoid()
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Basic ' + btoa(`${username}:${password}`),
},
body: JSON.stringify({
statement: `INSERT INTO store (KEY, VALUE)
VALUES ("${id}", ${JSON.stringify({ id, ...input })});
`,
}),
})
if (!response.ok) {
throw new Error(await response.text())
}
return {
id,
...input,
}
} catch (err) {
return null
}
}
That's it! You now have a GraphQL mutation to create new products that are stored in Couchbase.
Make sure to update the statement
to include the name of your database. store
was used above but you will have something different. Also, make sure to use your bucket and collection name
Now we're ready to run the Grafbase CLI that will start the GraphQL API locally:
npx grafbase dev
You can visit http://127.0.0.1:4000
and execute a GraphQL mutation!
mutation {
productCreate(
input: { name: "Stickers", slug: "stickers", price: 1000, onSale: true }
) {
id
name
}
}
We have products in our Couchbase database but no way to read them. Let's change that!
We already have the Product
type defined, so all we need to do is update the grafbase.config.ts
to include a new GraphQL query we'll call products
and attach the resolver file:
g.query('products', {
resolver: 'products/all',
returns: g.ref(product).optional().list().optional(),
})
Next, create the file resolvers/products/all.ts
and add the following:
const username = process.env.COUCHBASE_USERNAME
const password = process.env.COUCHBASE_PASSWORD
const url = process.env.COUCHBASE_URL
export default async function ProductsAllResolver() {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Basic ' + btoa(`${username}:${password}`),
},
body: JSON.stringify({
statement: `SELECT * FROM store`,
}),
})
if (!response.ok) {
throw new Error(await response.text())
}
const { results } = await response.json()
return results?.map(({ store }) => store) ?? []
} catch (err) {
return []
}
}
That's it! We can now query for all products from our Couchbase database using the new products
GraphQL query.
Open Pathfinder at http://127.0.0.1:4000
and execute the following GraphQL query:
{
products {
id
name
slug
price
onSale
}
}
That's it! We're now ready to configure edge caching so when we deploy this to the edge with Grafbase, we avoid making unnecessary trips to the Couchbase database.
Open grafbase.config.ts
and update the default export
to include a new cache
configuration:
export default config({
graph: g,
cache: {
rules: [
{
types: ['Query'],
maxAge: 60,
},
],
},
auth: {
rules: rules => {
rules.public()
},
},
})
This configuration tells Grafbase to:
- Cache all queries for 60 seconds
- Enable public access to the Grafbase Edge Gateway
You can and should use the Grafbase CLI when building locally (or in a branch) but you will need to deploy to Grafbase to take advantage of GraphQL Edge Caching.
Follow these steps to deploy to production:
- Signup for a Grafbase account
- Create a new project
- Make sure to add all the required environment variables when deploying
- Update your host (Netlify, Vercel, Fly, etc.) with the new GraphQL API endpoint that Grafbase supplied for your new project.
That's it!
Grafbase is programmed to autonomously deploy a fresh gateway each time it identifies a change to grafbase/grafbase.config.ts
. Consequently, if you need to adjust any cache settings, including parameters like maxAge
, staleWhileRevalidate
, and mutationInvalidation
, you're free to do so.