diff --git a/docs/docs/graphql-reference.md b/docs/docs/graphql-reference.md index 288faf82ad633..989c635ef468d 100644 --- a/docs/docs/graphql-reference.md +++ b/docs/docs/graphql-reference.md @@ -8,6 +8,8 @@ This page will walk you through a series of GraphQL queries, each designed to de You'll be using GraphiQL, an interactive editor you can also use [while building your Gatsby site](/tutorial/part-five/#introducing-graphiql). +For more information, read about [why Gatsby uses GraphQL](/docs/why-gatsby-uses-graphql/). + ## Basic query Let's start with the basics, pulling up the site `title` from your `gatsby-config.js`'s `siteMetaData`. Here the query is on the left and the results are on the right. diff --git a/docs/docs/graphql.md b/docs/docs/graphql.md index 68075d35aa546..a223ff052af70 100644 --- a/docs/docs/graphql.md +++ b/docs/docs/graphql.md @@ -29,6 +29,6 @@ Which returns this: Notice how the query signature exactly matches the returned JSON signature. This is possible because in GraphQL, you query against a `schema` that is the representation of your available data. Don't worry about where the schema comes from right now, Gatsby takes care of organizing all of your data for you and making it discoverable with a tool called GraphiQL. GraphiQL is a UI that lets you 1) run queries against your data in the browser, and 2) dig into the structure of data available to you through a data type explorer. -If you want to know more about what GraphQL is and why Gatsby uses it, check out this [conceptual guide](/docs/querying-with-graphql/) on querying data with GraphQL. +If you want to know more about GraphQL, you can read more about [why Gatsby uses it](/docs/why-gatsby-uses-graphql/) and check out this [conceptual guide](/docs/querying-with-graphql/) on querying data with GraphQL. [[guidelist]] diff --git a/docs/docs/images/why-gql-images.gif b/docs/docs/images/why-gql-images.gif new file mode 100644 index 0000000000000..c4e263d57029f Binary files /dev/null and b/docs/docs/images/why-gql-images.gif differ diff --git a/docs/docs/images/why-gql-no-data.png b/docs/docs/images/why-gql-no-data.png new file mode 100644 index 0000000000000..ce207f92f6159 Binary files /dev/null and b/docs/docs/images/why-gql-no-data.png differ diff --git a/docs/docs/images/why-gql-playground.png b/docs/docs/images/why-gql-playground.png new file mode 100644 index 0000000000000..80970adecd978 Binary files /dev/null and b/docs/docs/images/why-gql-playground.png differ diff --git a/docs/docs/images/why-gql-product-json.png b/docs/docs/images/why-gql-product-json.png new file mode 100644 index 0000000000000..03a94b4a40041 Binary files /dev/null and b/docs/docs/images/why-gql-product-json.png differ diff --git a/docs/docs/images/why-gql-with-context.png b/docs/docs/images/why-gql-with-context.png new file mode 100644 index 0000000000000..553b8ac6e995e Binary files /dev/null and b/docs/docs/images/why-gql-with-context.png differ diff --git a/docs/docs/page-query.md b/docs/docs/page-query.md index 5603ddeab916f..2b58f43bacf98 100644 --- a/docs/docs/page-query.md +++ b/docs/docs/page-query.md @@ -6,6 +6,8 @@ Gatsby's `graphql` tag enables page components to retrieve data via GraphQL quer In this guide, you will learn [how to use the `graphql` tag](/docs/page-query#add-the-graphql-query) in your pages, as well as go a little deeper into [how the `graphql` tag works](/docs/page-query#how-does-the-graphql-tag-work). +If you’re curious, you can also read more about [why Gatsby uses GraphQL](/docs/why-gatsby-uses-graphql/). + ## How to use the `graphql` tag in pages ### Add `description` to `siteMetadata` diff --git a/docs/docs/querying-with-graphql.md b/docs/docs/querying-with-graphql.md index 39ce4c23a4197..7cda6f0d65d7d 100644 --- a/docs/docs/querying-with-graphql.md +++ b/docs/docs/querying-with-graphql.md @@ -21,6 +21,8 @@ the browser when needed by your components. ## Why is GraphQL so cool? +For a more in-depth look, read [why Gatsby uses GraphQL](/docs/why-gatsby-uses-graphql/). + - Eliminate frontend data boilerplate — no need to worry about requesting & waiting for data. Just ask for the data you need with a GraphQL query and it'll show up when you need it - Push frontend complexity into queries — many data transformations can be done at _build-time_ within your GraphQL queries - It's the perfect data querying language for the often complex/nested data dependencies of modern applications @@ -336,6 +338,8 @@ export const query = graphql` ## Further reading +- [Why Gatsby Uses GraphQL](/docs/why-gatsby-uses-graphql/) + ### Getting started with GraphQL - http://graphql.org/learn/ diff --git a/docs/docs/using-gatsby-without-graphql.md b/docs/docs/using-gatsby-without-graphql.md index 5ee2ae9cd382f..c4ea34e460cac 100644 --- a/docs/docs/using-gatsby-without-graphql.md +++ b/docs/docs/using-gatsby-without-graphql.md @@ -104,4 +104,5 @@ If you're building a small site, one efficient way to build it is to pull in uns ## Further reading -Amberley Romo's guide to [using Gatsby without GraphQL](/blog/2018-10-25-using-gatsby-without-graphql/) +- Amberley Romo's guide to [using Gatsby without GraphQL](/blog/2018-10-25-using-gatsby-without-graphql/) +- [Why Gatsby Uses GraphQL](/docs/why-gatsby-uses-graphql/) diff --git a/docs/docs/why-gatsby-uses-graphql.md b/docs/docs/why-gatsby-uses-graphql.md new file mode 100644 index 0000000000000..61a3c76009c0c --- /dev/null +++ b/docs/docs/why-gatsby-uses-graphql.md @@ -0,0 +1,409 @@ +--- +title: Why Gatsby Uses GraphQL +--- + +A common question about Gatsby is, “Why does Gatsby use GraphQL? Doesn’t it generate static files?” + +Without providing some context, it can seem like GraphQL is overkill for something like Gatsby. In this document, you’ll see what problems arise when creating pages, and how those problems can be solved using GraphQL. + +## Create a page without any data + +<iframe + title="Screencast on egghead of creating pages in Gatsby from hard-coded React components." + src="https://egghead.io/lessons/gatsby-create-a-gatsby-page-without-any-data/embed" + class="egghead-video" + width=600 height=348 +/> + +Video hosted on [egghead.io][egghead]. + +For any kind of pages that aren’t directly created in `src/pages/`, you’ll need Gatsby’s [`createPages` Node API](/docs/node-apis/#createPages) to create pages programmatically. + +All that’s required to create a page is a `path` where it should be created and the component that should be rendered there. + +For example, if you had the following component: + +```jsx:title=src/templates/no-data.js +import React from "react" + +const NoData = () => ( + <section> + <h1>This Page Was Created Programmatically</h1> + <p> + No data was required to create this page — it’s just a React component! + </p> + </section> +) + +export default NoData +``` + +You could programmatically create a page at `/no-data/` by adding the following to `gatsby-node.js`: + +```js:title=gatsby-node.js +exports.createPages = ({ actions: { createPage } }) => { + createPage({ + path: "/no-data/", + component: require.resolve("./src/templates/no-data.js"), + }) +} +``` + +After running `npm run develop`, you’ll see the following at `localhost:8000/no-data/`: + + + +In the simplest cases, this is all that’s required for building pages with Gatsby. However, you’ll often want to pass data to the page so that the template component is reusable. + +## Create a page with hard-coded data + +<iframe + title="Screencast on egghead of creating pages from hard-coded context data in Gatsby." + src="https://egghead.io/lessons/gatsby-create-a-gatsby-page-with-hard-coded-data/embed" + class="egghead-video" + width=600 height=348 +/> + +Video hosted on [egghead.io][egghead]. + +To pass data to the created pages, you’ll need to pass `context` to the `createPage` call. + +In `gatsby-node.js`, we can add context like so: + +```js:title=gatsby-node.js +exports.createPages = ({ actions: { createPage } }) => { + createPage({ + path: "/page-with-context/", + component: require.resolve("./src/templates/with-context.js"), + context: { + title: "We Don’t Need No Stinkin’ GraphQL!", + content: "<p>This is page content.</p><p>No GraphQL required!</p>", + }, + }) +} +``` + +The `context` property accepts an object, and we can pass in any data we want the page to be able to access. + +> **NOTE:** There are a few reserved names that _cannot_ be used in `context`. They are: `path`, `matchPath`, `component`, `componentChunkName`, `pluginCreator___NODE`, and `pluginCreatorId`. + +When Gatsby creates pages, it includes a prop called `pageContext` and sets its value to `context`, so we can access any of the values in our component: + +```jsx:title=src/templates/with-context.js +import React from "react" + +const WithContext = ({ pageContext }) => ( + <section> + <h1>{pageContext.title}</h1> + <div dangerouslySetInnerHTML={{ __html: pageContext.content }} /> + </section> +) + +export default WithContext +``` + +Start the development server with `npm run develop` and visit `localhost:8000/page-with-context/` to see the created page: + + + +In some cases, this approach may be enough. However, it’s often necessary to create pages from data that can't be hard-coded. + +## Create pages from JSON with images + +<iframe + title="Screencast on egghead of creating pages from JSON data in Gatsby." + src="https://egghead.io/lessons/gatsby-create-pages-from-json-with-images/embed" + class="egghead-video" + width=600 height=348 +/> + +Video hosted on [egghead.io][egghead]. + +In many cases, the data for pages can't feasibly be hard-coded into `gatsby-node.js`. More likely it will come from an external source, such as a third-party API, local Markdown, or JSON files. + +For example, you might have a JSON file with post data: + +```json:title=data/products.json +[ + { + "title": "Vintage Purple Tee", + "slug": "vintage-purple-tee", + "description": "<p>Keep it simple with this vintage purple tee.</p>", + "price": "$10.00", + "image": "/images/amberley-romo-riggins.jpg" + }, + { + "title": "Space Socks", + "slug": "space-socks", + "description": "<p>Get your feet into these spaced-out black socks with a Gatsby purple border and heel.</p>", + "price": "$10.00", + "image": "/images/erin-fox-and-sullivan.jpg" + }, + { + "title": "This Purple Hat Is Blazing Fast", + "slug": "purple-hat", + "description": "<p>Add more blazingly blazing speed to your wardrobe with this solid purple laundered chino twill hat.</p>", + "price": "$10.00", + "image": "/images/david-bailey-cat-hat.jpg" + } +] +``` + +The images need to be added to the `/static/images/` folder. (This is where things start to get hard to manage — the JSON and the images aren’t in the same place.) + +Once the JSON and the images are added, you can create product pages by importing the JSON into `gatsby-node.js` and loop through the entries to create pages: + +```js:title=gatsby-node.js +exports.createPages = ({ actions: { createPage } }) => { + const products = require("./data/products.json") + products.forEach(product => { + createPage({ + path: `/product/${product.slug}/`, + component: require.resolve("./src/templates/product.js"), + context: { + title: product.title, + description: product.description, + image: product.image, + price: product.price, + }, + }) + }) +} +``` + +The product template still uses `pageContext` to display the product data: + +```jsx:title=src/templates/product.js +import React from "react" + +const Product = ({ pageContext }) => ( + <div> + <h1>{pageContext.title}</h1> + <img + src={pageContext.image} + alt={pageContext.title} + style={{ float: "left", marginRight: "1rem", width: 150 }} + /> + <p>{pageContext.price}</p> + <div dangerouslySetInnerHTML={{ __html: pageContext.description }} /> + </div> +) + +export default Product +``` + +Run `npm run develop`, then open `localhost:8000/product/space-socks/` to see one of the generated products. + + + +This gets the job done, but it has a few shortcomings that are going to get more complicated as time goes on: + +1. The images and the product data are in different places in the source code. +2. The image paths are absolute from the _built_ site, not the source code, which makes it confusing to know how to find them from the JSON. +3. The images are unoptimized, and any optimization you do would have to be manual. +4. To create a preview listing of all products, we’d need to pass _all_ of the product info in `context`, which will get unweildy as the number of products increases. +5. It’s not very obvious where data is coming from in the templates that render the pages, so updating the data might be confusing later. + +To overcome these limitations, Gatsby introduces GraphQL as a data management layer. + +## Create pages using GraphQL + +There’s a bit more up-front setup required to get data into GraphQL, but the benefits far outweigh the cost. + +Using `data/products.json` as an example, by using GraphQL we’re able to solve all of the limitations from the previous section: + +1. The images can be colocated with the products in `data/images/`. +2. Image paths in `data/products.json` can be relative to the JSON file. +3. Gatsby can automatically optimize images for faster loading and better user experience. +4. We no longer need to pass all product data into `context` when creating pages. +5. Data is loaded using GraphQL in the components where it’s used, making it much easier to see where data comes from and how to change it. + +### Add the necessary plugins to load data into GraphQL + +<iframe + title="Screencast on egghead of adding data to GraphQL in Gatsby." + src="https://egghead.io/lessons/gatsby-make-data-queryable-in-graphql-with-gatsby/embed" + class="egghead-video" + width=600 height=348 +/> + +Video hosted on [egghead.io][egghead]. + +In order to load the product and image data into GraphQL, we need to add a few [Gatsby plugins](/plugins/). Namely, we need plugins to: + +- Load the JSON file into Gatsby’s internal data store, which can be queried using GraphQL ([`gatsby-source-filesystem`](/packages/gatsby-source-filesystem/)) +- Convert JSON files into a format we can query with GraphQL ([`gatsby-transformer-json`](/packages/gatsby-transformer-json/)) +- Optimize images ([`gatsby-plugin-sharp`](/packages/gatsby-plugin-sharp/)) +- Add data about optimized images to Gatsby’s data store ([`gatsby-transformer-sharp`](/packages/gatsby-transformer-sharp/)) + +In addition to the plugins, we’ll use [`gatsby-image`](/packages/gatsby-image/) to display the optimized images with lazy loading. + +Install these packages using the command line: + +```bash +npm install --save gatsby-source-filesystem gatsby-transformer-json gatsby-plugin-sharp gatsby-transformer-sharp gatsby-image +``` + +Then add them to `gatsby-config.js`: + +```js:title=gatsby-config.js +module.exports = { + plugins: [ + { + resolve: "gatsby-source-filesystem", + options: { + path: "./data/", + }, + }, + "gatsby-transformer-json", + "gatsby-transformer-sharp", + "gatsby-plugin-sharp", + ], +} +``` + +To check that this worked, let’s use the GraphQL Playground, which is available during development, by running: + +``` +GATSBY_GRAPHQL_IDE=playground yarn develop +``` + +> **NOTE:** The `GATSBY_GRAPHQL_IDE=playground` part of this command is optional. Adding it enables the GraphQL Playground instead of GraphiQL, which is an older interface for exploring GraphQL. + +You can explore the available data using the “Docs” tab at the right. + +One of the available options is `allProductsJson`, which contains “edges”, and those contain “nodes”. + +The JSON transformer plugin has created one node for each product, and inside the node we can select the data we need for that product. + +You can write a query to select each product’s slug like this: + +```graphql +{ + allProductsJson { + edges { + node { + slug + } + } + } +} +``` + +Test this query by entering it into the left-hand panel of the GraphQL Playground, then pressing the play button in the top center. + +The results will appear in the panel between the query and the docs, and they’ll look like this: + + + +### Generate pages with GraphQL + +<iframe + title="Screencast on egghead of generating pages using GraphQL in Gatsby." + src="https://egghead.io/lessons/gatsby-create-pages-in-gatsby-using-graphql/embed" + class="egghead-video" + width=600 height=348 +/> + +Video hosted on [egghead.io][egghead]. + +In `gatsby-node.js`, we can use the GraphQL query we just wrote to generate pages. + +```js:title=gatsby-node.js +exports.createPages = async ({ actions: { createPage }, graphql }) => { + const results = await graphql(` + { + allProductsJson { + edges { + node { + slug + } + } + } + } + `) + + results.data.allProductsJson.edges.forEach(edge => { + const product = edge.node + + createPage({ + path: `/gql/${product.slug}/`, + component: require.resolve("./src/templates/product-graphql.js"), + context: { + slug: product.slug, + }, + }) + }) +} +``` + +You need to use the `graphql` helper that’s available to the [`createPages` API](/docs/node-apis/#createPages) to execute the query. To make sure that the result of the query comes back before continuing, use [`async`/`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). + +The results that come back are very similar to the contents of `data/products.json`, so you can loop through the results and create a page for each. + +However, note that you’re only passing the `slug` in `context` — you’ll use this in the template component to load more product data. + +As you’ve already seen, the `context` argument is made available to the template component in the `pageContext` prop. To make queries more powerful, Gatsby _also_ exposes everything in `context` as a GraphQL variable, which means you can write a query that says, in plain English, “Load data for the product with the slug passed in `context`.” + +Here’s what that looks like in practice: + +```jsx:title=src/templates/product-graphql.js +import React from "react" +import { graphql } from "gatsby" +import Image from "gatsby-image" + +export const query = graphql` + query($slug: String!) { + productsJson(slug: { eq: $slug }) { + title + description + price + image { + childImageSharp { + fluid { + ...GatsbyImageSharpFluid + } + } + } + } + } +` + +const Product = ({ data }) => { + const product = data.productsJson + + return ( + <div> + <h1>{product.title}</h1> + <Image + fluid={product.image.childImageSharp.fluid} + alt={product.title} + style={{ float: "left", marginRight: "1rem", width: 150 }} + /> + <p>{product.price}</p> + <div dangerouslySetInnerHTML={{ __html: product.description }} /> + </div> + ) +} + +export default Product +``` + +A few notes about this file: + +1. The result of the query is added to the template component as the `data` prop. +2. The image path was automatically converted by the Sharp transformer into a “child node” that includes optimized versions of the image. +3. The query uses a [GraphQL fragment](https://www.gatsbyjs.org/packages/gatsby-image/#fragments) to query all the required data for optimized images. GraphQL fragments _do not work_ in the GraphQL Playground. +4. The `img` tag has been swapped out for a `gatsby-image` component named `Image`. Instead of a `src` attribute, it accepts an object with optimized image data. + +Save this file, run `npm run develop`, then open `localhost:8000/gql/purple-hat/`: + + + +The image is now optimized and lazy loaded. + +After the initial setup, loading data with GraphQL is fairly similar to directly loading JSON, but it provides extra benefits like automatically optimizing images and keeping the data loading in the same place where it’s used. + +GraphQL is certainly not required, but the benefits of adopting GraphQL are significant. GraphQL will simplify the process of building and optimizing your pages, so it’s considered a best practice for structuring and writing Gatsby applications. + +[egghead]: https://egghead.io/playlists/why-gatsby-uses-graphql-1c319a1c diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 447d082f2664b..f2571b9101f03 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -112,6 +112,8 @@ - title: Querying Your Data with GraphQL link: /docs/graphql/ items: + - title: Why Gatsby Uses GraphQL + link: /docs/why-gatsby-uses-graphql/ - title: Understanding GraphQL Syntax link: /docs/graphql-reference/ - title: Introducing GraphiQL