I recently needed to stand up a demo app that invoked an AWS AppSync GraphQL API. The existing endpoint (not available publicly) returned restaurant data for a given zip with the following schema.
type ZipData {
zip: String
timezone: String
restaurants: [RestaurantData]
}
type RestaurantData {
name: String
latitude: String
longitude: String
}
type Query {
get_restaurants_by_zip(zip: String!): ZipData
}
schema {
query: Query
}
The app I wanted to build was throwaway, and my goal was to make it happen effortlessly, during a single lunch break. I was told to try Redwood.js, AWS Amplify, Sanity.io, Next.js via prisma-examples, and some zero-code tools, including ReTool.
Redwood.js
Let’s start with Redwood.js …
Create an App
nvm use 12
yarn create redwood-app ./redwood-js-appsync-graphql-demo
cd ./redwood-js-appsync-graphql-demo
Add a Homepage
yarn redwood generate page home /
Add an Input Box for the Zip Code
When the form is submitted, state (including zip code) will change.
import { Link, routes } from '@redwoodjs/router'
import { useState } from 'react'
import { Form, TextField, Submit } from '@redwoodjs/forms'
const HomePage = () => {
const [zip, setZip] = useState()
const onSubmit = (data) => {
setZip(data.zip)
}
return (
<div>
<Form onSubmit={onSubmit}>
<TextField name="zip" placeholder="Zip code" maxLength="5" />
<Submit>Go</Submit>
</Form>
</div>
)
}
export default HomePage
Add graphql-request
yarn add -W graphql-request
Add the GraphQL Query
import { GraphQLClient } from 'graphql-request'
export const getRestaurantsByZip = async (zip) => {
const endpoint = process.env.APPSYNC_API_ENDPOINT_URL
const graphQLClient = new GraphQLClient(endpoint, {
headers: {
'x-api-key': process.env.APPSYNC_API_KEY
},
})
const query = gql`query GetZip($zip: String!) {
get_restaurants_by_zip(zip: $zip) {
zip
timezone
restaurants {
name
}
}
}`
return graphQLClient.request(query, { zip: zip })
}
Wire Up GraphQL Results
const onSubmit = (data) => {
getRestaurantsByZip(data.zip).then(rc => {
setZip(rc.get_restaurants_by_zip.zip)
})
}
return (
<div>
...
<div>
{zip &&
<div>
<h2>{zip.zip}</h2>
<h3>{zip.timezone}</h3>
</div>
}
{zip && zip.restaurants.map(r =>
<div>{r.name}</div>
)}
</div>
</div>
)
Not Taking Advantage of Redwood.js
So far I used Redwood.js similarly to how one would use Rails without models, views or controllers, just to host some Ruby code on a web page. Redwood has lots of features, including cells and side-loading. Seems like a missed opportunity! So I asked the Redwood.js community to help me leverage Redwood.js better. The answer is more generally described in the documentation under Server-Side API Integration.
Expose Restaurants Side-Loading
Add api/src/graphql/restaurants.sdl.js
with the schema that our Redwood.js service will return.
import gql from 'graphql-tag'
export const schema = gql`
type Restaurant {
name: String
longitude: String
latitude: String
}
type Zip {
zip: String
timezone: String
restaurants: [Restaurant]
}
type Query {
restaurants(zip: String!): Zip
}
`
Fetch Data from the AppSync API
Wire up calls to the AppSync endpoint in api/src/lib/db.js
.
import { GraphQLClient } from 'graphql-request'
export const request = async (query, variables) => {
const endpoint = process.env.APPSYNC_API_ENDPOINT_URL
const graphQLClient = new GraphQLClient(endpoint, {
headers: {
'x-api-key': process.env.APPSYNC_API_KEY
},
})
return await graphQLClient.request(query, variables)
}
Add api/src/services/restaurants.js
that loads restaurant data using the above endpoint.
import { request } from 'src/lib/db'
import { gql } from 'graphql-request'
export const restaurants = async (args) => {
const query = gql`query GetZip($zip: String!) {
get_restaurants_by_zip(zip: $zip) {
zip
timezone
restaurants {
name
longitude
latitude
}
}
}`
var data = await request(query, args)
return data.get_restaurants_by_zip
}
Add a Restaurants Cell
yarn redwood generate cell restaurants
The cell makes a query to the Redwood.js service to fetch restaurants.
export const QUERY = gql`query($zip: String!) {
restaurants(zip: $zip) {
zip
timezone
restaurants {
name
longitude
latitude
}
}
}`
export const Loading = () => <div>Loading ...</div>
export const Empty = () => <div>No zip yet!</div>
export const Failure = ({ error, zip }) => {
return <div>Error loading zip {zip}: {error.message}</div>
}
export const Success = ({ restaurants }) => {
return <div>
<div>
<h2>{restaurants.zip}</h2>
<h3>{restaurants.timezone}</h3>
</div>
{restaurants.restaurants.map(r =>
<div>
<div>{r.name}</div>
</div>
)}
</div>
}
Wire up the cell in the homepage.
const HomePage = () => {
const [zip, setZip] = useState()
const onSubmit = (data) => {
setZip(data.zipCode)
}
return (
<div>
{zip && <RestaurantsCell zip={zip} />}
</div>
)
}
Put it All Together
Create a .env
file with APPSYNC_API_ENDPOINT_URL
and APPSYNC_API_KEY
and run the app with yarn redwood dev
.
See github.com/dblock/redwood-js-appsync-graphql-demo for the complete, working code, along with some Google maps.