Andrew Welch
Posted on March 23, 2020
Using the Craft CMS “headless” with the GraphQL API
Craft CMS 3.3 added a GraphQL layer that gives your website a formalized, structured API out of the box. Here’s how to use GraphQL + Craft CMS as a “headless” CMS
Andrew Welch / nystudio107
GraphQL & Craft CMS
This article explores coding patterns you might find useful when working with Craft CMS’s GraphQL API via real-world examples, and also why you might want to use GraphQL to begin with.
GraphQL describes itself with the following pithy tagline:
And that’s exactly what it is. It’s a neutral layer that sits on top of whatever API layer you have, from databases like MySQL, Postgres, Mongo, etc. to custom built solutions.
In August 2019, Pixel & Tonic added GraphQL to Craft CMS 3.3, and vastly improved it in Craft CMS 3.4.
Prior to this, we relied on Mark Huot’s CraftQL plugin for GraphQL, as discussed in the Using VueJS + GraphQL to make Practical Magic article.
Should you use Mark’s CraftQL plugin or should you use Craft CMS’s first-party GraphQL implementation?
Unless you need mutations (the ability to change data in the Craft CMS backend via GraphQL), using the Craft CMS first-party GraphQL implementation in Craft 3.3 or later is the way to go.
Craft CMS’s first-party GraphQL API doesn’t currently support mutations, but it does offer a robust, performant data source for headless Craft CMS.
Why use GraphQL?
As a web developer, you’d use GraphQL if you were writing a frontend that is separate from the backend. You’d be using Craft CMS as a “headless” CMS for its excellent content authoring experience.
Perhaps you’re writing the frontend in a framework like React, Vue.js, Svelte, or one of the frameworks that layers on top of them like Next.js, Nuxt.js, Gatsby, Gridsome, or Sapper.
Or perhaps you’re jumping on the JAMstack bandwagon, and just need a self-hosted CMS like Craft CMS to manage your content.
Or maybe the project has an iPhone app that needs to communicate with a content management system on the backend for data.
In those cases, you don’t have access to Twig or the Element Queries to interact with Craft CMS and your data, but you still want to reap the benefits of Craft CMS’s wonderful & flexible content authoring experience.
You could write a custom API for Craft CMS using the Element API, but that can be a significant amount of work, and you’ll end up with something boutique, rather than an industry standard.
Enter the GraphQL
This is where GraphQL for Craft CMS excels. By virtue of creating your content models in Craft CMS, you automatically get a GraphQL API to access it, with no extra work on your part.
It’s also self-documenting, and strictly typed. You don’t need to define the GraphQL schemas, because you’ve already implicitly done that in Craft.
It just works.
You just need to be using Craft CMS “Pro” edition, and you’ll see GraphQL in your CP, and you can immediately start exploring the GraphQL API using the explorer (which is provided by a frontend tool called GraphiQL):
While it’s beyond the scope of this article to teach you GraphQL (there are many great resources online for that), there are a few things to note here:
- In the upper-left pane, is the GraphQL query specifying what we’re searching
- In the lower-left pane are variables we pass into the query, in JSON format
- The middle pane is the data returned from the GraphQL endpoint as a result of our query
- To the right pane is a searchable, documented schema reference
The fun thing about using this GraphQL explorer is that you can learn by just poking around.
You can press Option-Spacebar at any time for a list of auto-completed items that can appear in any given context. You can also use the Documentation Explorer to see your entire schema.
Combined with the official Craft CMS GraphQL API documentation, you can get pretty far just by exploring around in the GraphQL Explorer.
The Way of the GraphQL
Before we dive into some queries in JavaScript, I have a few tips I’d like to pass along to you to make your life with GraphQL easier.
One nice feature of the Craft CMS implementation of GraphQL is that it has a caching layer for your queries. While this is normally great for performance reasons, when you’re developing a site, you really want it off to avoid potentially dealing with cached results.
You can do this via the enableGraphQlCaching setting in your config/general.php file:
<?php
/**
* General Configuration
*
* All of your system's general configuration settings go in here. You can see a
* list of the available settings in vendor/craftcms/cms/src/config/GeneralConfig.php.
*
* @see \craft\config\GeneralConfig
*/
return [
// Craft config settings from .env variables
'enableGraphQlCaching' => (bool)getenv('ENABLE_GQL_CACHING'),
];
Here I set it to an Environment Variable, but you can set it to true or false directly if you prefer.
Craft CMS also has a headlessMode setting. This is intended for situations where Craft CMS will never be used to render any content on the frontend.
Due to limitations in how this works in Craft CMS 3.3, I recommend leaving it set to false, since elements lack URIs in the 3.3 implementation of headlessMode. This causes many plugins to not work properly, and other undesired behavior.
However, Pixel & Tonic has made improvements to how the headlessMode setting works in Craft CMS 3.4, so you can set it to true if you want Craft CMS to never render content, and simply be a data/API provider via GraphQL.
Something many people aren’t aware of is that you can use GraphQL in Twig if you want to:
{# Set the GraphQL query #}
{% set query %}
query organismsQuery($needle: String!)
{
entries(section: "organisms", search: $needle, orderBy: "title") {
title,
slug,
id
}
}
{% endset %}
{# Set the variables to pass into the query #}
{% set variables = {
'needle': 'Botulism'
} %}
{# Query the data via Twig! #}
{% set data = gql(query, variables) %}
{% dd data %}
The gql() Twig filter was added in Craft CMS 3.3.12. Why would you ever want to do such a thing?
Usually, you wouldn’t. You’d just use Element Queries instead… but it can be a nice way to experiment and play around with queries in a familiar/comfortable environment.
In fact, it hammers home a very important conceptual point with GraphQL in Craft CMS:
GraphQL is a thin-ish layer in Craft CMS that handles mapping from a GraphQL queries to Element Queries. It works in both directions:
- The GraphQL schema is derived from the underlying Craft CMS Sections, Element content models, etc.
- GraphQL queries are internally mapped to Element Queries, which are then executed
The reason this is an important concept is that if you want to figure out how to do something in GraphQL, figure out how to do it via an Element Query first, and then work your way backwards.
So it can then be handy to use gql() in Twig, because you can write your Element Query as you normally would. Get it working the way you want it to, and then construct & test the analogous GraphQL query in the same place.
Finally, if you’re using PhpStorm, check out the GraphQL Schema Auto-Completion with PhpStorm article for blissful auto-complete query writing in your code.
Controller of Fury
I mentioned earlier that you get a GraphQL API without having to do any work with Craft CMS. While this is true, having a little custom controller as part of your site module can be helpful.
I always have a site module as part of my Craft CMS setups, as discussed in the Enhancing a Craft CMS 3 Website with a Custom Module article.
You always start with no custom code, but inevitably you end up needing a little piece here or there. And if you have a module in place already, it makes adding this in really easy.
In our case, we want a custom controller that our frontend can ping to:
- actionGetCsrf() — get the current CSRF token to validate POST request submissions
- actionGetGqlToken() — get a particular GraphQL token for access permissions
- actionGetFieldOptions() — get the options in a Dropdown field in Craft CMS
POST requests are used to send data from the website along to an endpoint. While we could use GET for simple requests like actionGetGqlToken(), we need to send data to other endpoints like actionGetFieldOptions(), so we might as well use POST for all requests.
Here’s what our custom controller looks like:
<?php
/**
* Site module for Craft CMS 3.x
*
* An example module for Craft CMS 3 that lets you enhance your websites with a
* custom site module
*
* @link https://nystudio107.com/
* @copyright Copyright (c) 2019 nystudio107
*/
namespace modules\sitemodule\controllers;
use Craft;
use craft\web\Controller;
use yii\web\Response;
/**
* @author nystudio107
* @package SiteModule
* @since 1.0.0
*/
class SiteController extends Controller
{
// Constants
// =========================================================================
const GQL_TOKEN_NAME = 'Site GQL Token';
// Protected Properties
// =========================================================================
protected $allowAnonymous = [
'get-csrf',
'get-gql-token',
'get-field-options',
];
// Public Methods
// =========================================================================
/**
* @inheritdoc
*/
public function beforeAction($action): bool
{
// Disable CSRF validation for get-csrf POST requests
if ($action->id === 'get-csrf') {
$this->enableCsrfValidation = false;
}
return parent::beforeAction($action);
}
/**
* @return Response
*/
public function actionGetCsrf(): Response
{
return $this->asJson([
'name' => Craft::$app->getConfig()->getGeneral()->csrfTokenName,
'value' => Craft::$app->getRequest()->getCsrfToken(),
]);
}
/**
* @return Response
*/
public function actionGetGqlToken(): Response
{
$result = null;
$tokens = Craft::$app->getGql()->getTokens();
foreach ($tokens as $token) {
if ($token->name === self::GQL_TOKEN_NAME) {
$result = $token->accessToken;
}
}
return $this->asJson([
'token' => $result,
]);
}
/**
* Return all of the field options from the passed in array of $fieldHandles
*
* @return Response
*/
public function actionGetFieldOptions(): Response
{
$result = [];
$request = Craft::$app->getRequest();
$fieldHandles = $request->getBodyParam('fieldHandles');
foreach ($fieldHandles as $fieldHandle) {
$field = Craft::$app->getFields()->getFieldByHandle($fieldHandle);
if ($field) {
$result[$fieldHandle] = $field->options;
}
}
return $this->asJson($result);
}
}
While this custom controller isn’t necessary for using your GraphQL endpoint, it does make the experience a bit nicer. We grab the CSRF token, and send that CSRF token along with further requests, to allow Yii2 to validate the data submissions.
Schemas & Tokens
GraphQL in Craft CMS has a concept of Schemas and Tokens:
- Schemas — schemas you can think of these as permissions, in a way, in that they define what parts of the underlying CraftCMS content you want exposed
- Tokens — tokens are they “keys” that you pass along with your GraphQL queries, and they link to a schema
So what I do is:
- Get the CSRF token for the current session
- Use that CSRF to obtain a specific GraphQL token used for API access
- Use that GraphQL token in all GraphQL request to the endpoint
In many cases, you won’t need to do this because you’ll just have one Public Schema that defines your GraphQL API. But if you want to potentially have varying levels of access, you’d created multiple tokens and use them to what schema you can use.
If you just want to use the special Public Schema, you don’t need any token at all. Just don’t send a bearer token down with your GraphQL queries, and it’ll just use whatever schema is defined in Public Schema.
N.B.: In our case, we want public access to do the data. The CRSF and GraphQL tokens are just for lightweight auth, it is not treated as a locked down “secret” for auth’ing. For that you’d want something like JSON Web Tokens (JWT), check out the Craft JWT Auth plugin.
Controller & GraphQL XHRs
Let’s get into some JavaScript that we use to communicate with our custom controller endpoints, and also the Craft CMS GraphQL endpoint.
First here is a little xhr.js helper to configure and execute generic XHRs:
// Configure the XHR api endpoint
export const configureXhrApi = (url) => ({
baseURL: url,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
// Execute an XHR to our api endpoint
export const executeXhr = async(api, variables, callback) => {
// Execute the XHR
try {
const response = await api.post('', variables);
if (response.data) {
callback(response.data);
}
} catch (error) {
console.error(error);
}
};
The X-Requested-With: XMLHttpRequest header lets Craft CMS know that this is an “AJAX” request, which can affect how the request is processed.
Here’s how we get the CSRF from our custom controller endpoint:
import axios from 'axios';
import { configureXhrApi, executeXhr } from '../utils/xhr.js';
const CSRF_ENDPOINT = '/actions/site-module/site/get-csrf';
// Fetch & commit the CSRF token
export const getCsrf = async({commit, state}) => {
const api = axios.create(configureXhrApi(CSRF_ENDPOINT));
let variables = {
};
// Execute the XHR
await executeXhr(api, variables, (data) => {
commit('setCsrf', data);
});
};
The above function is a Vuex Action, but Vuex is just used as a convenient place to store data. It’s not important that you use Vuex yourself to get something out of the examples presented here.
The code is just creating an Axios instance, executing the XHR, and stashing the returned CSRF data for future use.
We just use Axios here as opposed to something like Apollo Client just because all we really need is a lightweight way to communicate without GraphQL endpoint. We could also use the built-in Fetch API, but Axios handles some edge-case situations and browser compatibility for us.
Then this is how we get the GraphQL token:
import axios from 'axios';
import { configureGqlApi, executeGqlQuery } from '../utils/gql.js';
// Fetch & commit the GraphQL token
export const getGqlToken = async({commit, state}) => {
const api = axios.create(configureXhrApi(TOKEN_ENDPOINT));
let variables = {
...(state.csrf && { [state.csrf.name]: state.csrf.value }),
};
// Execute the XHR
await executeXhr(api, variables, (data) => {
commit('setGqlToken', data);
});
};
Once again, this is a Vuex Action, but it doesn’t have to be. The important part is that it’s using the CSRF we obtained earlier, and sending that along with a request for our GraphQL token, which it then saves for future use.
The funky ...(state.csrf && { [state.csrf.name]: state.csrf.value }), line deserves some explanation. It’s using the JavaScript Spread Syntax to add a key/value pair to the variables object.
But it’s doing it in a conditional way, using the behavior of the JavaScript && Logical Operator to only spread the { [state.csrf.name]: state.csrf.value } object into variables only if state.csrf is truthy.
This works because JavaScript will return the object if state.csrf is truthy, and if not, it’ll return state.csrf itself, and the … spread syntax is smart enough to know not to spread null, so it spread nothing into variables.
Finally we have some helper JavaScript in gql.js (very similar to the xhr.js above) which configures and creates an Axios instance for querying Craft’s GraphQL endpoint:
// Configure the GraphQL api endpoint
export const configureGqlApi = (url, token) => ({
baseURL: url,
headers: {
'X-Requested-With': 'XMLHttpRequest',
...(token && { 'Authorization': `Bearer ${token}` }),
}
});
// Execute a GraphQL query by sending an XHR to our api endpoint
export const executeGqlQuery = async(api, query, variables, callback) => {
// Execute the GQL query
try {
const response = await api.post('', {
query: query,
variables: variables
});
if (callback && response.data.data) {
callback(response.data.data);
}
// Log any errors
if (response.data.errors) {
console.error(response.data.errors);
}
} catch (error) {
console.error(error);
}
};
This just adds an additional a Bearer Token to the header which Craft CMS will use to link to the schema that the request will have permission to access, and it adds some error logging specific to the GraphQL implementation.
A Simple Query
Let’s look at a simple query that uses the infrastructure we’ve described above:
export const organismsQuery =
`
query organismsQuery($needle: String!)
{
entries(section: "organisms", search: $needle, orderBy: "title") {
title,
slug,
id
}
}
`;
This is the same query we looked at in the GraphQL Explorer previously, which is executed as follows:
import axios from 'axios';
import { configureGqlApi, executeGqlQuery } from '../utils/gql.js';
const GRAPHQL_ENDPOINT = '/api';
// Fetch & commit the Organisms array from a GraphQL query
export const getOrganisms = async({commit, state}) => {
const token = state.gqlToken ? state.gqlToken.token : null;
const api = axios.create(configureGqlApi(GRAPHQL_ENDPOINT, token));
let variables = {
needle: 'useInSearchForm:*'
};
// Execute the GQL query
await executeGqlQuery(api, organismsQuery, variables, (data) => {
if (data.entries) {
commit('setOrganisms', data.entries);
}
});
};
This is once again a Vuex Action, which uses the GraphQL token we’ve already obtained, and passes in useInSearchForm:* as a search parameter in needle.
This is looking for a lightswitch field in Craft CMS with the handle useInSearchForm, so that it’ll only return entries from the organisms section that have that lightswitch on.
This returns data that looks like this:
[
{
"title": "Bacillus cereus",
"slug": "bacillus-cereus",
"id": "10226"
},
{
"title": "Bacterial toxin",
"slug": "bacterial-toxin",
"id": "14472"
},
]
Querying a Single Thing
Normally all of your GraphQL queries will return an array of data, even if you’re only asking for one thing. While this consistency is nice, there are times when you really only ever want one thing returned.
In Craft CMS 3.4, singular versions of all of the queries were made available:
- assets() → asset()
- categories() → category()
- entries() → entry()
- globalSets() → globalSet()
- tags() → tag()
- users() → user()
This is analogous to the .all() → .one() methods used on Element Queries.
So we can leverage this for our queries where we only ever want one thing returned, so we don’t have to do ugly [0] array syntax:
export const outbreakDetailQuery =
`
query outbreakDetailQuery($slug: [String])
{
entry(section: "outbreaks", slug: $slug) {
title,
url,
slug,
...on outbreaks_outbreaks_Entry {
summary,
beginningDate,
states,
country,
hasTest,
testResults,
productSubjectToRecall,
totalIll,
numberIllByCaseDefinitionKnown,
probableIll,
possibleIll,
confirmedIll,
hospitalized,
numberHospitalized,
anyDeaths,
numberOfDeaths,
recallLinks {
col1
url
},
reportReferenceLinks {
col1
url
},
brands {
title
},
locations {
title
},
organisms {
title
},
tags {
title
},
vehicles {
title
}
}
}
}
`;
This rather large query exemplifies the GraphQL mindset, which is that you need to specify all of the data that you want returned. This is unlike Element Queries, where by default you get all of the data back.
Here’s what it looks like executing this query:
import axios from 'axios';
import { configureGqlApi, executeGqlQuery } from '../utils/gql.js';
const GRAPHQL_ENDPOINT = '/api';
// Fetch & commit an Outbreak detail from a GraphQL query
export const getOutbreakDetail = async({commit, state}) => {
const token = state.gqlToken ? state.gqlToken.token : null;
const outbreakSlug = state.outbreakSlug || null;
const api = axios.create(configureGqlApi(GRAPHQL_ENDPOINT, token));
let variables = {
slug: outbreakSlug
};
// Execute the GQL query
await executeGqlQuery(api, outbreakDetailQuery, variables, (data) => {
if (data.entry) {
commit('setOutbreakDetail', data.entry);
}
});
};
This code is almost exactly identical to the code we used to for getOrganisms(), except that we pass in a slug to look for in our entry() query rather than a search parameter.
Here’s what the data returned from this query looks like:
{
"title": "2017 Multistate Outbreak of Salmonella Infantis Linked Mangoes (Suspected)",
"url": "http://outbreakdatabase.test/outbreaks/2017-multistate-outbreak-of-salmonella-infantis-linked-mangoes-suspected",
"slug": "2017-multistate-outbreak-of-salmonella-infantis-linked-mangoes-suspected",
"summary": "In the summer of 2017 federal, state and local health officials investigated an outbreak of Salmonella Infantis. The suspected vehicle of transmission was mangoes. Forty eight outbreak associated cases were reported by 14 states. Reported and estimated illness onset dates ranged from June 30, 2017 to August 31, 2017. Among 42 cases with available information, there were 15 reported hospitalizations. No deaths were reported. \nThe cluster investigation was assigned CDC 1708MLJFX-1.",
"beginningDate": "2017-06-01T07:00:00+00:00",
"states": [
"CA",
"IL",
"IN",
"MI",
"MN",
"NJ",
"NY",
"NC",
"OH",
"OK",
"TX",
"WA",
"WI"
],
"country": "us",
"hasTest": "yes",
"testResults": "JFXX01.0010",
"productSubjectToRecall": "yes",
"totalIll": 48,
"numberIllByCaseDefinitionKnown": "yes",
"probableIll": null,
"possibleIll": null,
"confirmedIll": 48,
"hospitalized": "yes",
"numberHospitalized": 15,
"anyDeaths": "",
"numberOfDeaths": null,
"recallLinks": [
{
"col1": "",
"url": ""
}
],
"reportReferenceLinks": [
{
"col1": "",
"url": ""
}
],
"brands": [],
"locations": [
{
"title": "Retail"
}
],
"organisms": [
{
"title": "Salmonella"
}
],
"tags": [
{
"title": "salmonellosis"
},
{
"title": "salmonella"
},
{
"title": "mangoes"
},
{
"title": "infantis"
}
],
"vehicles": [
{
"title": "Mangoes"
}
]
}
Dynamic Parameter Queries
But what if we’re doing something more complicated, like a faceted search, where we want to narrow down the search criteria based on a series of optional search parameters from the user such as shown in this video:
Prior to Craft CMS 3.4, you couldn’t do this via the GraphQL API. But Craft CMS 3.4 added two key features to make it possible
- It’s now possible to query for elements by their custom field values via GraphQL. (#5208)
- It’s now possible to filter element query results by their related elements using relational fields’ element query params (e.g. publisher(100) rather than relatedTo({targetElement: 100, field: 'publisher'})). (#5200)
These are both huge in terms of making the GraphQL API flexible, so hat’s off to Andris for making it happen.
This allows us to construct a dynamic query like this:
export const outbreaksQuery = (additionalParams) => {
let queryAdditionalParams = '';
let entryAdditionalParams = '';
additionalParams.forEach((item) => {
queryAdditionalParams += `, $${item.fieldName}: [QueryArgument]`;
entryAdditionalParams += `, ${item.fieldName}: $${item.fieldName}`;
});
return `
query outbreaksQuery($needle: String!${queryAdditionalParams})
{
entries(section: "outbreaks", search: $needle${entryAdditionalParams}) {
title,
url,
slug,
...on outbreaks_outbreaks_Entry {
summary,
beginningDate,
vehicles {
title
},
organisms {
title
},
tags {
title
}
}
}
}
`;
};
Note that this is a function, not a simple returned template literal as in the previous examples.
This allows us to dynamically add parameters to our query based on whatever has been pushed into the additionalParams array.
So the base query looks like this:
query outbreaksQuery($needle: String!)
{
entries(section: "outbreaks", search: $needle) {
title,
url,
slug,
...on outbreaks_outbreaks_Entry {
summary,
beginningDate,
vehicles {
title
},
organisms {
title
},
tags {
title
}
}
}
}
…and the variables we pass in look like this:
{
needle: "vomit"
}
If the user then also adds in an organism to search on (which in our case is a related entry in the Craft CMS backend), the query will look like this:
query outbreaksQuery($needle: String!, $organisms: [QueryArgument])
{
entries(section: "outbreaks", search: $needle, organisms: $organisms) {
title,
url,
slug,
...on outbreaks_outbreaks_Entry {
summary,
beginningDate,
vehicles {
title
},
organisms {
title
},
tags {
title
}
}
}
}
…and the variables we pass in look like this:
{
needle: "vomit",
organisms: "4425"
}
The 4425 number is the element ID of the related organism entry in the Organisms section. It could also be an array of IDs, or anything else you’d normally pass in as a relational field’s element query params.
This technique works for an arbitrary number of additional faceted search criteria, which gives the user quite a bit of power in the search form.
Here’s the Vuex Action code that implements it:
import axios from 'axios';
import { configureGqlApi, executeGqlQuery } from '../utils/gql.js';
const GRAPHQL_ENDPOINT = '/api';
// Push additional params
const pushAdditionalParam = (state, dataFieldName, dbFieldName, additionalParams) => {
let fieldValue = state.searchForm ? state.searchForm[dataFieldName] || '' : '';
if (fieldValue.length) {
// Special case for fields
if (dbFieldName === 'states') {
// As per https://docs.craftcms.com/v3/checkboxes-fields.html#querying-elements-with-checkboxes-fields
fieldValue = `*"${fieldValue}"*`
}
additionalParams.push({
fieldName: dbFieldName,
fieldValue: fieldValue,
});
}
};
// Fetch & commit the Outbreaks array from a GraphQL query
export const getOutbreaks = async({commit, state}) => {
const token = state.gqlToken ? state.gqlToken.token : null;
const keywords = state.searchForm ? state.searchForm.keywords || '*' : '*';
// Construct the additional parameters
let additionalParams = [];
for (const [key, value] of Object.entries(additionalParamFields)) {
pushAdditionalParam(state, key, value, additionalParams);
}
// Configure out API endpoint
const api = axios.create(configureGqlApi(GRAPHQL_ENDPOINT, token))
// Construct the variables object
let variables = {
needle: keywords,
};
additionalParams.forEach((item) => {
variables[item.fieldName] = item.fieldValue;
});
// Execute the GQL query
await executeGqlQuery(api, outbreaksQuery(additionalParams), variables, (data) => {
if (data.entries) {
commit('setOutbreaks', data.entries);
}
});
};
This pushAdditionalParam() function just does a little magic to map from the name of a property in our Vue component to the name of the field in the database.
Then it special-cases for the states field, which is Checkboxes field in Craft CMS. To query for an item in a Checkboxes field, you need to use the format '"foo"'.
As per what we discussed previously, we figured this out by looking up how to do it in an Element Query, and then using that in our GraphQL query.
Then the getOutbreaks() function then just dynamically builds the additionalParams array and variables object, which then is passed along to our query.
The data returned from this query looks like this:
[
{
"title": "1997 Botulism After Consumption of Home-Pickled Eggs, Illinois",
"url": "http://outbreakdatabase.test/outbreaks/1997-botulism-after-consumption-of-home-pickled-eggs-illinois",
"slug": "1997-botulism-after-consumption-of-home-pickled-eggs-illinois",
"summary": "On November 23, 1997, a previously healthy man became nauseated, vomited, and complained of abdominal pain. During the next 2 days, he developed double vision, difficult movement of joints, and respiratory impairment. He was hospitalized and placed on mechanical ventilation. His physical examination confirmed multiple cranial nerve abnormalities, including extraocular motor palsy and diffuse flaccid paralysis. Botulism was diagnosed, and antibotulinum toxin was administered. His blood serum demonstrated the presence of type B botulinum toxin. A food history revealed no exposures to home-canned products; however, the patient had eaten pickled eggs that he had prepared seven days before his symptoms began. The patient recovered after a prolonged period of supportive care. \n\nThe pickled eggs were prepared using a recipe that consisted of hard-boiled eggs, commercially prepared beets and hot peppers, and vinegar. The intact hard-boiled eggs were peeled and punctured with toothpicks then combined with the other ingredients in a glass jar that closed with a metal screw-on lid. The mixture was stored at room temperature and occasionally was exposed to sunlight. \n\nCultures revealed Clostridium botulinum type B, and type B toxin was detected in samples of the pickled egg mixture, the pickling liquid, beets, and egg yolk. The commercially sold peppers contained no detectable toxin or Clostridium botulinum bacteria. Beets from the original commercial containers were not available. The pH of the pickling liquid was 3.5 (i.e., adequate to prevent C. botulinum germination and toxin formation, however, the pH of egg yolk, although not tested for the investigation, is normally 6.8., conducive for bacteria growth and toxin production. Inadequate acidification and lack of refrigeration enhanced the risk of bacterial contamination of this home-prepared food.",
"beginningDate": "1997-11-01T08:00:00+00:00",
"vehicles": [
{
"title": "Eggs, Pickled eggs"
}
],
"organisms": [
{
"title": "Botulism"
}
],
"tags": [
{
"title": "c.+botulinum"
},
{
"title": "clostridium+botulinum"
},
{
"title": "c.+bot."
}
]
}
]
Tapping Out
Hopefully these real-world examples of using Craft CMS’s GraphQL API in “headless” CMS setups has been helpful to you!
Craft CMS is an excellent choice if you want to combine a great content authoring experience on the backend with a “headless” CMS that serves up data to a frontend via a GraphQL API.
If you’re a plugin or custom module developer, there are also some other nice new features in Craft CMS 3.4 for GraphQL:
- Plugins can now modify the GraphQL schema via craft\gql\TypeManager::EVENT_DEFINE_GQL_TYPE_FIELDS
- Plugins can now modify the GraphQL permissions via craft\services\Gql::EVENT_REGISTER_GQL_PERMISSIONS
This allows for great flexibility in terms of extending the existing Craft CMS GraphQL API.
Happy querying!
Further Reading
If you want to be notified about new articles, follow nystudio107 on Twitter.
Copyright ©2020 nystudio107. Designed by nystudio107
Posted on March 23, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.