Integrating with Json placeholder
In this tutorial we will implement a basic integration with Json Placeholder.
We will create a Todo list based on the data from here.
Overview
To add a new data type from a new data source we will need to integrate with a few of Falcon Platform's concepts.
- APIs (to fetch the data)
- Extensions (to format the data using GraphQL)
- Queries (to decide what data is needed)
- Components (to render the data on the frontend)
n.b. In this tutorial we will be adding everything directly into our repository client/src
and server/src
. If you plan to reuse your functionality it's highly recommended you add it to your own NPM
packages.
1. Add your API connection
We need to create the file server/src/falcon-jsonplaceholder-api/index.js
.
Notice the naming convention [package]-[datasource]-api
.
In this file we create our fetch
requests to the API.
server/src/falcon-jsonplaceholder-api/index.js
const { ApiDataSource } = require('@deity/falcon-server-env');
module.exports = class JsonPlaceholderApi extends ApiDataSource {
async todoList(_, { pagination }) {
const query = {};
if (pagination) {
query._limit = pagination.perPage;
query._start = pagination.page;
}
const todos = await this.get('todos', query);
return {
items: todos
};
}
async todo(_, { id }) {
return this.get(`todos/${id}`);
}
};
There is a lot going on here, some of which will be explained later. It's important we extend ApiDataSource
so our core packages can do the heavy lifting.
2. Add your extension and GraphQL schema
Now we need to add our extension. You'll need to create 2 files:
|-server
|-src
|-falcon-todos-extension
index.js
schema.graphql
Again, notice the naming convention. [package]-[data_type]-extension
. Unlike the API package, we name this based on the data type not the source. This is because, we may at any point want to swap out the source but use the same extension.
The index.js
file can look like this:
server/src/falcon-todos-extension/index.js
module.exports = () => ({});
At this point we don't need to do anything custom here. Provided our queries are named correctly (see below) they will be mapped automatically to our API methods.
Your schema.graphql
file should look like this:
server/src/falcon-todos-extension/schema.graphql
extend type Query {
todoList(pagination: PaginationInput): TodoList! @cache(ttl: 15)
todo(id: ID!): Todo! @cache(ttl: 15)
}
type TodoList {
items: [Todo!]!
pagination: Pagination
}
type Todo {
userId: Int
id: ID
title: String
completed: Boolean
}
In this file we are defining what each query should accept and return.
The magic part... notice the name of our 2 queries, they are named todoList
and todo
.
Now go back and look at server/src/falcon-jsonplaceholder-api/index.js
.
You will notice that the methods share the same name, if you keep the name the same these methods will be bound automatically.
Pagination
We are passing PaginationInput
to our todoList
query but not defining it. This is already defined in @deity/falcon-data/src/Pagination/Pagination.ts
.
It contains to bits of information perPage
and page
.
@deity/falcon-data/src/Pagination/Pagination.ts
export type PaginationInput = {
perPage: number,
page: number
};
We map these to query parameters in server/src/falcon-jsonplaceholder-api/index.js
.
server/src/falcon-jsonplaceholder-api/index.js
if (pagination) {
query._limit = pagination.perPage;
query._start = pagination.page * pagination.perPage;
}
return this.get('todos', query);
The end result is https://jsonplaceholder.typicode.com/todos?_limit=[perPage]&_start=[page]
3. Include your extension and API
Now we've added our files we need to make sure falcon-server
is using them and they are mapped together.
This is done in your server/config/default.json
file.
server/config/default.json
{
...
"apis": {
...
"jsonplaceholder": {
"package": "./src/falcon-jsonplaceholder-api/index.js",
"config": {
"host": "jsonplaceholder.typicode.com",
"protocol": "https"
}
}
},
"extensions": {
...
"falcon-todos-extension": {
"package": "./src/falcon-todos-extension",
"config": {
"api": "jsonplaceholder"
}
}
}
}
In this file we define our API (jsonplaceholder
) and pass this to our extensions config "api": "jsonplaceholder"
.
Within the API config we've also defined host
and protocol
. These are used automatically by the get
functions in server/src/falcon-jsonplaceholder-api/index.js
.
4. Test your graphQL
You should be all set to test your extension now.
If you've got falcon-server
running and it's on port 4000
(this is the default port) then you can test your queries here. If you get results returned then you're in business.
5. Accessing your data in Falcon Client.
Now we need to create query components. You will often see these named like [package]-[data_type]-data
e.g. falcon-blog-data
.
Because we aren't adding our code to NPM
packages we are going to add the code to our client/src/components
directory.
|-client
|-src
|-components
|- Todo
index.js
TodoItemQuery.js
TodoListQuery.js
Our index.js
file will just be used to export our components.
client/src/components/Todo/index.js
export * from './TodoListQuery';
export * from './TodoItemQuery';
We now need to create a Query
component for each data type, TodoItem
and TodoList
.
client/src/components/Todo/TodoItemQuery.js
import gql from 'graphql-tag';
import { Query } from '@deity/falcon-data';
const GET_TODO_ITEM = gql`
query TodoItem($id: ID!) {
todo(id: $id) {
title
completed
}
}
`;
export class TodoItemQuery extends Query {
static defaultProps = {
query: GET_TODO_ITEM
};
}
In this file we define our GraphQL query and pass it to our component that extends import { Query } from '@deity/falcon-data'
.
We are just grabbing title
and completed
but if you want the id
or userId
defined in our server/src/falcon-todos-extension/schema.graphql
you can.
Our TodoListQuery
looks very similar:
client/src/components/Todo/TodoListQuery.js
import gql from 'graphql-tag';
import { Query } from '@deity/falcon-data';
const GET_TODO_LIST = gql`
query TodoList($pagination: PaginationInput) {
todoList(pagination: $pagination) {
items {
title
completed
}
}
}
`;
export class TodoListQuery extends Query {
static defaultProps = {
query: GET_TODO_LIST
};
}
6. Rendering your data in a component
Let's use our data in some components...
For this we need to create 2 more components.
|-client
|-src
|-components
|- Todo
TodoItem.js
TodoList.js
We also need to export them from our index.js
file.
client/src/components/Todo/index.js
...
export * from './TodoList';
export * from './TodoItem';
Todo Item Component
client/src/components/Todo/TodoItem.js
import React from 'react';
import { TodoItemQuery } from './TodoItemQuery';
export const TodoItem = ({ todoId }) => {
return (
<>
<TodoItemQuery variables={{ id: todoId }}>
{({ data: { todo } }) => {
if (todo) {
return <h1>{todo.title}</h1>;
}
return null;
}}
</TodoItemQuery>
</>
);
};
There is a few things going on here. We import our TodoItemQuery
component.
This accepts a variables
prop. This is an object of variables that are passed to our query. In this case, the ID of our todo.
Each query returns data
, this returns the response from our query...in our case, todo
.
Todo List Component
client/src/components/Todo/TodoList.js
import React from 'react';
import { TodoListQuery } from './TodoListQuery';
export const TodoList = ({ pagination = {} }) => {
return (
<>
<TodoListQuery variables={{ pagination }}>
{({ data: { todoList = {} } }) => {
const { items = [] } = todoList;
if (items.length) {
const todoItems = items.map(item => {
const { completed, title } = item;
return completed ? (
<li>{title}</li>
) : (
<li>
<del>{title}</del>
</li>
);
});
return <ul>{todoItems}</ul>;
}
return null;
}}
</TodoListQuery>
</>
);
};
This works in exactly the same way as our TodoItem
component.
7. Use our components.
We've done all the hard work, we now just need to use our components somewhere in our application.
To access our component in an standardized way we will first export it to the component root.
client/src/components/index.js
...
export * from './Todo';
Now that it is available from the components directory I'm going to add them to the homepage.
client/src/pages/home/Home.js
...
import {
TodoItem,
TodoList
} from '../../components';
const Home = () => {
...
return (
<PageLayout css={{ paddingTop: 72, paddingBottom: 72, gridGap: 72 }}>
...
<TodoItem todoId={2} />
<Divider variant="fullWidth" />
<TodoList pagination={{ perPage: 9, page: 1 }} />
...
</PageLayout>
);
};
export default Home;
8. All done.
That's it. You can use this same concept to get data from any API.