Build and Deploy a GraphQL API to the Edge with MySQL and PlanetScale — Part 4
productCreate mutation
A GraphQL mutation is a type of operation in GraphQL that modifies data on the server and typically look something like this:
mutation {
doSomething(a: String, b: Int) {
someField
}
}
doSomething
is the name of the mutationa
andb
are names of the arguments passed to mutationsString
andInt
are the data types of the argumentssomeField
is the name of a field returned by the mutation
Let's begin by creating a mutation to create new products in the database.
If you selected TypeScript as the configuration type when using grafbase init
you will have the file grafbase/grafbase.config.ts
. Inside here we will add the type for Product
with the following fields:
id
name
slug
price
onSale
import { config, g } from '@grafbase/sdk'
const product = g.type('Product', {
id: g.id(),
name: g.string(),
slug: g.string(),
price: g.int(),
onSale: g.boolean()
})
export default config({
schema: g
})
Below the Product
definition, we can now add the createProduct
mutation. We'll need to create an input type used by the mutation and configure the mutation itself, which points to the resolver file:
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()
})
Now let's create the code that runs when the GraphQL mutation productCreate
is executed. This code is known as a GraphQL resolver.
Create the file grafbase/resolvers/products/create.ts
and begin by exporting a default async
function, it can be named whatever you like but we'll call it ProductsCreate
.
import { connect } from '@planetscale/database'
import { config } from '../../lib'
const conn = connect(config)
export default async function ProductsCreate(_, { input }) {
const fields: string[] = []
const placeholders: string[] = []
const values: (string | number | boolean)[] = []
}
In the code above we will destructure input
from the second arguments passed to the resolver function. This input
argument will be populated by the fields passed to the GraphQL mutation productCreate.
Also in this code we create three new variables — fields
, placeholders
and values
that we will use to collect the necessary data to pass onto the database request.
Next for each of the entries in the input
object we will check to see if the value is one of the expected types — string
, number
or boolean
. These values match the field types of the Product
type we created previously.
If the value is present and one of those types then we will push the field name onto the fields
array, add a new placeholder into the placeholders
array and add the value to the values
array.
Object.entries(input).forEach(([field, value]) => {
if (
value !== undefined &&
value !== null &&
(typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean')
) {
fields.push(`\`${field}\``)
placeholders.push('?')
values.push(value)
}
})
Next, we use the fields
and placeholders
inside of the SQL statement. We'll begin the statement by using INSERT INTO products
followed by the fields joined using ,
and an empty space then add the placeholders (e.g. ?, ?, ?, ?
).
const statement = `INSERT INTO products (${fields.join(
', '
)}) VALUES (${placeholders.join(', ')})`
Now we can pass this statement to conn.execute
and pass along the values:
const { insertId } = await conn.execute(statement, values)
One thing to note is that the id
and onSale
values will both be of the an integer type. What we will need to do is cast these values into the correct type and we can do that by passing a third argument to conn.execute()
:
const { insertId } = await conn.execute(statement, values, {
cast(field, value) {
switch (field.name) {
case 'id': {
return String(value)
}
case 'onSale': {
return Boolean(value)
}
default: {
return cast(field, value)
}
}
}
})
Once you added a basic try/catch
block to wrap the request and surface any database errors, you should have something that looks like this:
import { cast, connect } from '@planetscale/database'
import { config } from '../../lib'
const conn = connect(config)
export default async function ProductsCreate(_, { input }) {
const fields: string[] = []
const placeholders: string[] = []
const values: (string | number | boolean)[] = []
Object.entries(input).forEach(([field, value]) => {
if (
value !== undefined &&
value !== null &&
(typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean')
) {
fields.push(`\`${field}\``)
placeholders.push('?')
values.push(value)
}
})
const statement = `INSERT INTO products (${fields.join(
', '
)}) VALUES (${placeholders.join(', ')})`
try {
const { insertId } = await conn.execute(statement, values, {
cast(field, value) {
switch (field.name) {
case 'id': {
return String(value)
}
case 'onSale': {
return Boolean(value)
}
default: {
return cast(field, value)
}
}
}
})
return {
id: insertId,
...input
}
} catch (error) {
console.log(error)
return null
}
}
That's all we need to successfully create a product in the database using a GraphQL mutation.
Now run the Grafbase local development server using the command below:
npx grafbase dev
Next open Pathfinder at http://localhost:4000
and execute the following mutation:
mutation {
productCreate(
input: { name: "Shoes", slug: "shoes", price: 1000, onSale: true }
) {
id
name
slug
onSale
price
}
}
You can repeat this mutation as many times as you like with unique content. Make sure you don't use an slug
.