Skip to main content

Integrating a 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...

todo list component

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.