Deploy GraphQL APIs at the Edge with Couchbase

Deploy GraphQL APIs at the Edge with Couchbase

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:

Couchbase Documents Table

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:

  1. Accepts an input argument input type
  2. Invoke the resolver file create
  3. 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.

Get Started

Build your API of the future now.