Note: This is a work-in-progress that only implements queries, FQL is hard dude
The first FaunaDB project was the most basic possible integration, involving only a single GraphQL query from Redwood to Fauna to return a list of posts. All the create, update, and delete operations were performed directly on the database with commands in the Fauna Query Language.
The goal of this project is to rewrite the Example Todo app to work with Fauna. There are 4 services we will need to replicate with either FQL or through GraphQL.
- todos
- createTodo
- updateTodoStatus
- renameTodo
Implementing the todos
service will be mostly identical to posts
in the last project.
Original schema
type Todo {
id: Int!
body: String!
status: String!
}
type Query {
todos: [Todo]
}
type Mutation {
createTodo(body: String!): Todo
updateTodoStatus(id: Int!, status: String!): Todo
renameTodo(id: Int!, body: String!): Todo
}
Schema imported to Fauna
type Todo {
id: Int!
body: String!
status: String!
}
type Query {
todos: [Todo]
}
Schema provided by Fauna
directive @embedded on OBJECT
directive @collection(name: String!) on OBJECT
directive @index(name: String!) on FIELD_DEFINITION
directive @resolver(
name: String
paginated: Boolean! = false
) on FIELD_DEFINITION
directive @relation(name: String) on FIELD_DEFINITION
directive @unique(index: String) on FIELD_DEFINITION
scalar Date
scalar Time
scalar Long
type Todo {
body: String!
_id: ID!
id: Int!
status: String!
_ts: Long!
}
input TodoInput {
id: Int!
body: String!
status: String!
}
type TodoPage {
data: [Todo]!
after: String
before: String
}
type Query {
findTodoByID(id: ID!): Todo
todos(
_size: Int
_cursor: String
): TodoPage!
}
type Mutation {
createTodo(data: TodoInput!): Todo!
updateTodo(
id: ID!
data: TodoInput!
): Todo
deleteTodo(id: ID!): Todo
}
New Schema
type Todo {
body: String!
_id: ID!
id: Int!
status: String!
}
input TodoInput {
id: Int!
body: String!
status: String!
}
type TodoPage {
data: [Todo]!
after: String
before: String
}
type Query {
todos: TodoPage!
}
type Mutation {
createTodo(data: TodoInput!): Todo!
updateTodo(
id: ID!
data: TodoInput!
): Todo
deleteTodo(id: ID!): Todo
}
db
When you create a FaunaDB database you will need to generate a key and set it to the header of the GraphQL request. You could hardcode it directly after 'Bearer ', but you don’t want to risk committing your private keys. Instead you want to create an environment variable called FAUNADB_SECRET
.
import { GraphQLClient } from 'graphql-request'
export const request = async (query = {}) => {
const endpoint = 'https://graphql.fauna.com/graphql'
const graphQLClient = new GraphQLClient(endpoint, {
headers: {
authorization: 'Bearer ' + process.env.FAUNADB_SECRET
},
})
try {
return await graphQLClient.request(query)
} catch (error) {
console.log(error)
return error
}
}
You will also need to install the following dependencies:
yarn workspace api add graphql-request graphql
Original Services
import { db } from 'src/lib/db'
export const todos = () => db.todo.findMany()
export const createTodo = ({ body }) => db.todo.create({ data: { body } })
export const updateTodoStatus = ({ id, status }) =>
db.todo.update({
data: { status },
where: { id },
})
export const renameTodo = ({ id, body }) =>
db.todo.update({
data: { body },
where: { id },
})
We’ll start with just the todos
service, then move on to createTodo
.
Fauna todos service
import { request } from 'src/lib/db'
import { gql } from 'graphql-request'
export const todos = async () => {
const query = gql`
{
todos {
data {
body
status
}
}
}
`
const data = await request(query, 'https://graphql.fauna.com/graphql')
return data['todos']
}
HomePage
const HomePage = () => {
return (
<SC.Wrapper>
<SC.Title>Todo List</SC.Title>
<TodoListCell />
<AddTodo />
</SC.Wrapper>
)
}
The home page is divided into two main sections
- TodoListCell - Displays the list of todos
- AddTodo - Takes input to create new todos
Original TodoListCell
import styled from 'styled-components'
import TodoItem from 'src/components/TodoItem'
import { useMutation } from '@redwoodjs/web'
export const QUERY = gql`
{
todos {
id
body
status
}
}
`
const UPDATE_TODO_STATUS = gql`
mutation TodoListCell_CheckTodo($id: Int!, $status: String!) {
updateTodoStatus(id: $id, status: $status) {
id
__typename
status
}
}
`
export const Loading = () => <div>Loading...</div>
export const Success = ({ todos }) => {
const [updateTodoStatus] = useMutation(UPDATE_TODO_STATUS)
const handleCheckClick = (id, status) => {
updateTodoStatus({
variables: { id, status },
optimisticResponse: {
__typename: 'Mutation',
updateTodoStatus: { __typename: 'Todo', id, status: 'loading' },
},
})
}
const list = todos.map(todo => (
<TodoItem key={todo.id} {...todo} onClickCheck={handleCheckClick} />
))
return <SC.List>{list}</SC.List>
}
export const beforeQuery = (props) => ({
variables: props,
})
Fauna TodoListCell
export const QUERY = gql`
{
todos {
data {
body
status
}
}
}
`
export const Loading = () => <div>Loading...</div>
export const Success = ({ todos }) => {
const {data} = todos
const list = data.map(todo => (
<TodoItem key={todo.id} {...todo} />
))
return <SC.List>{list}</SC.List>
}
You can create a few example todos by opening the Fauna dashboard and going to Collections. Click New Document and enter a todo.
{ "id": 1, "body": "hello-on", "status": "on" }
{
"ref": Ref(Collection("Todo"), "282241154242576909"),
"ts": 1605424989845000,
"data": {
"id": 1,
"body": "hello-on",
"status": "on"
}
}
Create a second todo.
{ "id": 2, "body": "goodbye-off", "status": "off" }
{
"ref": Ref(Collection("Todo"), "282241160429175309"),
"ts": 1605424995730000,
"data": {
"id": 2,
"body": "goodbye-off",
"status": "off"
}
}
Request
Response
And that’s where I’m at right now. Next will be writing the createTodo
service. This will involve the following pieces of code:
AddTodo
import { useMutation } from '@redwoodjs/web'
import AddTodoControl from 'src/components/AddTodoControl'
import { QUERY as TODOS } from 'src/components/TodoListCell'
const CREATE_TODO = gql`
mutation AddTodo_CreateTodo($body: String!) {
createTodo(body: $body) {
id
__typename
body
status
}
}
`
const AddTodo = () => {
const [createTodo] = useMutation(CREATE_TODO, {
update: (cache, { data: { createTodo } }) => {
const { todos } = cache.readQuery({ query: TODOS })
cache.writeQuery({
query: TODOS,
data: { todos: todos.concat([createTodo]) },
})
},
})
const submitTodo = (body) => {
createTodo({
variables: { body },
optimisticResponse: {
__typename: 'Mutation',
createTodo: { __typename: 'Todo', id: 0, body, status: 'loading' },
},
})
}
return <AddTodoControl submitTodo={submitTodo} />
}
export default AddTodo
AddTodoControl
import styled from 'styled-components'
import { useState } from 'react'
import Check from 'src/components/Check'
const AddTodoControl = ({ submitTodo }) => {
const [todoText, setTodoText] = useState('')
const handleSubmit = (event) => {
submitTodo(todoText)
setTodoText('')
event.preventDefault()
}
const handleChange = (event) => {
setTodoText(event.target.value)
}
return (
<SC.Form onSubmit={handleSubmit}>
<Check type="plus" />
<SC.Body>
<SC.Input
type="text"
value={todoText}
placeholder="Memorize the dictionary"
onChange={handleChange}
/>
<SC.Button type="submit" value="Add Item" />
</SC.Body>
</SC.Form>
)
}
export default AddTodoControl