Use TakeShape with Redwood
Redwood is a Jamstack framework that lets you quickly and easily build your own GraphQL API on the same server that your frontend is served from. TakeShape's API Mesh allows you to integrate multiple external APIs into one GraphQL API. Put the two together, and you have a Jamstack project that can consume data from any source with GraphQL.
Let's go through the process of building a RedwoodJS project with TakeShape.
Follow Along
Follow along with this guide by checking out our starter project repo. Our starters let you instantly deploy a pre-configured TakeShape project.
Setting up Redwood
To create a Redwood project, you'll need to have NodeJS and Yarn installed. Node is a runtime that allows you to execute JavaScript without a browser. Yarn is a package manager alternative to NPM.
Installing Node
To get started with Redwood, you first need to install a version of NodeJS below version 15.0 at the time of this guide's publication. Redwood does not yet have a stable production-ready release, so it's not currently optimized for newer versions of Node. You can browse the different versions of node available here.
Installing Yarn
If you have npm already installed, use this command to install yarn:
npm install -g yarn
If you don't have npm, you can download the node version manager here.
Then you just have to run the command above to install yarn.
Creating your Redwood project
Navigate to a directory where you'd like to keep your Redwood project folder, then open the terminal.
Use yarn create redwood-app redwood-takeshape-example
to generate your redwood app.
Now change directories into your project folder, and open the project in your favorite IDE. Use the command yarn rw dev
to launch the dev server. Your project's homepage should launch at http://localhost:8910/
.
If it launches, you're ready to move on to the next step.
Having issues? Check out the Redwood docs.
Connecting your TakeShape project
Now that you have Redwood set up, you need to connect TakeShape to your Redwood project to begin pulling in data from the API Mesh. Let's go through the process now.
Getting your API Key and Project Endpoint
To connect to your TakeShape project, you need your API Endpoint and an API key.
If you haven't created a TakeShape project yet, follow this guide to do so.
If you already have a project, your API Endpoint will be in the TakeShape web client. In the home tab, look for the "Useful Snippets" section.
Copy the API Endpoint and save it somewhere secure.
To get an API Key, you'll have to create one. Read our docs on creating an API Key here.
Adding your API Key and Project Endpoint to your Redwood project
To access your TakeShape data, you'll need to use environment variables. Navigate to your project's root directory and open up the .env
file. You just need to add the two lines below to it, substituting in your API Key and project endpoint.
TAKESHAPE_API_KEY=Your-Key-Here
TAKESHAPE_ENDPOINT=Your-Endpoint-Here
Now your redwood project can pull your TakeShape data in with environment variables.
Using TakeShape on the frontend
There are two ways you can use TakeShape in your Redwood project: On the frontend, or through your Redwood GraphQL API.
If you connect it to your Redwood GraphQL API, you can then query that API from the frontend, and your Redwood API will fetch the TakeShape data for you. If you use it on the frontend, you can just install Apollo Client and query your TakeShape project's API directly.
This section talks about using TakeShape on the frontend.
Let's start by installing Apollo Client. Navigate to your project directory and install the appropriate packages:
yarn add @apollo/client graphql
Now we need to create an Apollo client. Start by opening web/src/App.js
.
Near the top of the file, import the following modules:
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client'
Now inside the App component function, just create a client object. You can copy the code below:
const client = new ApolloClient({
uri: process.env.TAKESHAPE_ENDPOINT,
cache: new InMemoryCache(),
headers: {
Authorization: `Bearer ${process.env.TAKESHAPE_API_KEY}`,
},
})
Now just wrap your <Routes />
component in an <ApolloProvider>
. The <Routes />
component is what Redwood uses to render the different routes to the pages of your site. Learn more here.
Your return statement in the App component function should look like this:
return (
<FatalErrorBoundary page={FatalErrorPage}>
<ApolloProvider client={client}>
<Routes />
</ApolloProvider>
</FatalErrorBoundary>
)
And here's the full App.js file.
import { FatalErrorBoundary } from '@redwoodjs/web'
import FatalErrorPage from 'src/pages/FatalErrorPage'
import Routes from 'src/Routes'
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client'
import './index.css'
const App = () => {
const client = new ApolloClient({
uri: process.env.TAKESHAPE_ENDPOINT,
cache: new InMemoryCache(),
headers: {
Authorization: `Bearer ${process.env.TAKESHAPE_API_KEY}`,
},
})
return (
<FatalErrorBoundary page={FatalErrorPage}>
<ApolloProvider client={client}>
<Routes />
</ApolloProvider>
</FatalErrorBoundary>
)
}
Now let's navigate to routes component at web/src/Routes
.
You'll see in the Routes component function that it returns a <Router>
component with <Route>
components inside. We're going to create a route.
So navigate to web/src/pages
and create a directory called HomePage
. Inside, create a HomePage.js
file.
The goal is to populate a page with products fetched from a Takeshape project, including their names, prices and associated photos.
To begin, at the top of the HomePage.js
file, import apollo's useQuery hook and gql tag:
import { gql, useQuery } from '@apollo/client'
Now let's make our component function.
It'll be a default export, and inside we'll call our useQuery hook.
export default () => {
const { loading, error, data } = useQuery()
}
We're not done with this section yet. Now we have to add our actual query. For this example, we're assuming you're using TakeShape to fetch ecommerce data. You can use our Shape Shop starter pattern when creating a project to get the appropriate schema to run this example. Or you can check out our starter repo.
Here's the query:
export default () => {
const { loading, error, data } = useQuery(
gql`
query {
getProductList {
items {
name
price
_id
image {
sourceUrl
}
}
}
}
`
)
}
The above query will fetch a list of products, and for each get the name, price, image and id of each.
All that's left to do is render the data out once it's fetched. To do that, we use the loading, error, data
booleans returned by the useQuery hook to conditionally render different things based on the state of the query.
- If it's loading, you want to display some text that says it's loading.
- If the data has arrived, you want to display it.
- If there's an error, display the error. The below code is how we handle that in this project:
if (loading) return <h1>Loading...</h1>
if (error) return <h1>Failed to load TakeShape. {error.data}</h1>
const { items } = data.getProductList
return (
<>
<h1>TakeShape Product List</h1>
<ul
style={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
rowGap: '3rem',
fontSize: '2rem',
}}
>
{items.map((product) => (
<li
key={product._id}
style={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
rowGap: '1rem',
}}
>
<span>
<b>{product.name}:</b>${product.price}
</span>
<img
alt={product.name}
src={product.image.sourceUrl}
style={{
width: '400px',
height: '300px',
}}
></img>
</li>
))}
</ul>
</>
)
The above code will render out our product list. Here's what the full component code should look like:
import { gql, useQuery } from '@apollo/client'
export default () => {
const { loading, error, data } = useQuery(
gql`
query {
getProductList {
items {
name
price
_id
image {
sourceUrl
}
}
}
}
`
)
if (loading) return <h1>Loading...</h1>
if (error) return <h1>Failed to load TakeShape. {error.data}</h1>
const { items } = data.getProductList
return (
<>
<h1>TakeShape Product List</h1>
<ul
style={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
rowGap: '3rem',
fontSize: '2rem',
}}
>
{items.map((product) => (
<li
key={product._id}
style={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
rowGap: '1rem',
}}
>
<span>
<b>{product.name}:</b>${product.price}
</span>
<img
alt={product.name}
src={product.image.sourceUrl}
style={{
width: '400px',
height: '300px',
}}
></img>
</li>
))}
</ul>
</>
)
}
In your terminal, enter yarn rw dev
to run the dev server.
Visit http://localhost:8910. Here's what the page should look like:
Using TakeShape on the backend
Now let's go through the process of integrating TakeShape into your Redwood GraphQL API.
If you're not aware, Redwood is similar to NextJS in that it gives you the tools to create your own backend for a Jamstack site. But Redwood takes things further by coming pre-packaged with Prisma integration and easy commands for generating a GraphQL API.
You consume this API on the frontend with Redwood's RedwoodApolloProvider
, which is a wrapper around Apollo Client.
This guide will teach you how to define a query that fetches a list of ecommerce products from a TakeShape project's API and display it in a Redwood project.
This guide will walk you through the whole process.
Setting up your API
To add TakeShape to your Redwood project's API, you must create a service that fetches the data for your custom query. Then you need to create a GraphQL schema that maps out the shape of the query and the data it expects back.
First, we'll create our service. In api/src/services
, add a file called getProductList.js
This service will fetch a product list from our TakeShape project. If you need a project to start with, check out our Shape Shop starter pattern. Want to get started more rapidly? Check out our starter repo.
Below is the code for our getProductList
service. There's nothing special going on, except that it fetches a product list from the TakeShape endpoint defined in our .env
file, with the API key defined in the same file.
We're also adding a special property to our query that doesn't exist on our TakeShape backend. It's called proof
, and we're using it just to prove that we're actually querying Redwood, which is then querying TakeShape for us.
export const getProductList = async () => {
try {
const result = await fetch(`${process.env.TAKESHAPE_ENDPOINT}`, {
headers: {
Authorization: `Bearer ${process.env.TAKESHAPE_API_KEY}`,
},
method: 'POST',
body: JSON.stringify({
query: `
query{
getProductList{
items{
_id
name
price
image{
sourceUrl
}
}
}
}`,
}),
})
const resultJSON = await result.json()
const { items } = resultJSON.data.getProductList
return {
items,
proof: 'This is proof that you got this data from redwood.',
}
} catch (error) {
console.log(error)
}
}
Next, we just have to define the schema in our graphql
folder. Navigate to api/src/graphql
, and create a file called getProductList.sdl.js
. You can learn more about how SDL files work with Redwood by reading their guide here.
Again, nothing special here. Just defining a schema that maps out the shape of a ProductList, which is returned by the getProductList query. A ProductList has an items
property, which is an array of products, and a proof
property. We added proof
to prove we're not just fetching from TakeShape directly. The Product
objects in the items
array have a name, price, id, and image.
Here's the schema:
export const schema = gql`
type ImageData {
sourceUrl: String.
}
type Product {
name: String.
price: Float.
_id: String.
image: ImageData.
}
type ProductList {
items: [Product.].
proof: String.
}
type Query {
getProductList: ProductList
}
`
Now we just have to do two things: Generate our types, and then rebuild our API. You can learn more about this process in the Redwood docs.
For our purposes, just enter yarn rw g types
into the terminal. It may take a minute or two to complete. When it's done, navigate to api/src/types/graphql.d.ts
and search for ProductList
, Product
, and ImageData
. If you see all three, your types were generated.
Here's what the relevant section of your types should look like:
export type ImageData = {
__typename?: 'ImageData'
sourceUrl: Scalars['String']
}
export type Product = {
__typename?: 'Product'
_id: Scalars['String']
image: ImageData
name: Scalars['String']
price: Scalars['Float']
}
export type ProductList = {
__typename?: 'ProductList'
items: Array<Product>
proof: Scalars['String']
}
Now let's build our api. Enter yarn rw build api
into the terminal.
It may take a second, but when the process is done, you're ready to use Redwood's Apollo Provider in the frontend.
Using TakeShape with your Redwood API on the frontend
Time to consume your Redwood API.
The process is similar to using a standard ApolloProvider
with ApolloClient. Navigate to web/src/App.js
to begin.
Near the top, add the following import command:
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
Now in the component function, we need to wrap our <Routes/>
component with the RedwoodApolloProvider
component. Routes
is the component Redwood uses to render out your routes. The return statement of the App
component function should look like this:
return(
<FatalErrorBoundary page={FatalErrorPage}>
<RedwoodApolloProvider>
<Routes />
</RedwoodApolloProvider>
</FatalErrorBoundary>
)
Here's what the full file should look like:
import { FatalErrorBoundary } from '@redwoodjs/web'
import FatalErrorPage from 'src/pages/FatalErrorPage'
import Routes from 'src/Routes'
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
import './index.css'
const App = () => {
return (
<FatalErrorBoundary page={FatalErrorPage}>
<RedwoodApolloProvider>
<Routes />
</RedwoodApolloProvider>
</FatalErrorBoundary>
)
}
export default App
Almost done.
Let's navigate to web/src/pages
and create a directory called AltHomePage
. We'll create a file in this directory called AltHomePage.js
. This will be our component for our alternate homepage, which we're creating so we can keep the homepage we've already made.
The code is the exact same as the code we wrote in the previous section, except we're imorting useQuery
and gql
from @redwoodjs/web
instead of @apollo/client
.
import { useQuery, gql } from '@redwoodjs/web'
We're also adding a single h2
to the code that will show our proof
property, which we defined on our getProductList
query:
<h2>{proof}</h2>
Here's what the full component looks like:
import { useQuery, gql } from '@redwoodjs/web'
export default () => {
const { loading, error, data } = useQuery(
gql`
query {
getProductList {
proof
items {
name
price
_id
image {
sourceUrl
}
}
}
}
`
)
if (loading) return <h1>Loading...</h1>
if (error) return <h1>Failed to load TakeShape. {error.data}</h1>
const { items, proof } = data.getProductList
return (
<>
<h1>TakeShape Alt Product List</h1>
<h2>{proof}</h2>
<ul
style={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
rowGap: '3rem',
fontSize: '2rem',
}}
>
{items.map((product) => (
<li
key={product._id}
style={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
rowGap: '1rem',
}}
>
<span>
<b>{product.name}:</b>${product.price}
</span>
<img
alt={product.name}
src={product.image.sourceUrl}
style={{
width: '400px',
height: '300px',
}}
></img>
</li>
))}
</ul>
</>
)
}
Now we just need to define the route to this component in web/src/Routes.js
.
Near the top of the file, import the AltHomePage
component:
import AltHomePage from './pages/AltHomePage/AltHomePage'
Now the Routes
component function just needs to return a Route
component with the AltHomePage
component as a prop:
const Routes = () => {
return (
<Router>
<Route path="/alt" page={AltHomePage} name="alt home" />
<Route notfound page={NotFoundPage} />
</Router>
)
}
Here's what the full component should look like:
import { Router, Route } from '@redwoodjs/router'
import AltHomePage from './pages/AltHomePage/AltHomePage'
const Routes = () => {
return (
<Router>
<Route path="/" page={HomePage} name="home" />
<Route path="/alt" page={AltHomePage} name="alt home" />
<Route notfound page={NotFoundPage} />
</Router>
)
}
export default Routes
Run the dev server with yarn rw dev
.
Visit http://localhost:8910/alt and the final page should look like this:
Further reading
For more information on customizing your Redwood API, check out Redwood's Cookbook article on the subject.
Still need help? Get in touch with us.