@forestryio/client
TypeScript icon, indicating that this package has built-in type declarations

0.1.35 • Public • Published

Introduction

This package allows you to interact with an automatically generated GraphQL API using TinaCMS. Included are multiple GraphQL adapters that give you a consistent GraphQL API regardless of your datasource.

For example, if your content is Git-backed, you might want to use your local content in development. While in your production Cloud Editing Environment, you can use our "Tina Teams" server to fetch your content. The API for both backends will be consistent, so you can easily switch between the two datasources without changing your site's code.

If you like to work in TypeScript, the @forestry/cli package can generate types using the same schema definition that the GraphQL adapters will use.

Install

Prerequisites

This guide assumes you have a working NextJS site. You can create one quickly with:

npx create-next-app --example blog-starter-typescript blog-starter-typescript-app

or

yarn create next-app --example blog-starter-typescript blog-starter-typescript-app

Install the client package

This package provides you with:

  • A ForestryClient class (which you can use as a TinaCMS API Plugin), that takes care of all interaction with the GraphQL server.
  • A useForestryForm hook, that you can use to hook into the Tina forms that let you edit your content.
npm install --save @forestryio/client

or

yarn add @forestryio/client

CLI package

You'll also likely want to install our CLI to help with development:

npm install --save-dev @forestryio/cli

or

yarn add --dev @forestryio/cli

This CLI performs a few functions:

  • Generates GraphQL queries (and optionally TypeScript types) based on your content's schema.
  • Auditing your content's schema and checking for errors.
  • Running a GraphQL server using the built-in filesystem adapter.

For full documentation of the CLI, see [here].(https://github.com/forestryio/graphql-demo/tree/client-documentation/packages/cli)

Implementation

We'll show how to use this package in a NextJS site

Create Dummy Content

Let's start by creating a simple dummy piece of content. Our goal will to be able to access and change this content through an auto-generated GraphQL API and Tina forms.

/_posts/welcome.md

---
title: This is my post
---

Configuration

Before we can define the schema of our content, we need set up some configuration. Create a .forestry directory and then create the following files.

.forestry/settings.yml

---
new_page_extension: md
auto_deploy: false
admin_path:
webhook_url:
sections:
  - type: directory
    path: _posts # replace this with the relative path to your content section
    label: Posts
    create: documents
    match: "**/*.md"
    new_doc_ext: md
    templates:
      - post # replace this with your template filename name
upload_dir: public/uploads
public_path: "/uploads"
front_matter_path: ""
use_front_matter_path: false
file_template: ":filename:"

These files will create a map our content to content models. In the above file, we declare any markdown files in our project should be a "post" type (we'll define this post type next).

Define Content Schema

Templates define the shape of different content models.

.forestry/front_matter/templates/post.yml

---
label: Post
hide_body: false
display_field: title
fields:
  - name: title
    type: text
    config:
      required: false
    label: Title
pages:
  - _posts/welcome.md # This keeps reference to all the pages using this template

Sourcing your content

Now that we have defined our content model, we can connect our site to the Tina.io Content API

Make sure your .tina directory is pushed to git

Creating a Tina.io app

The Tina.io content API connects to your Github repository, and puts the content behind Tina.io's expressive content API.

  • Navigate to Tina.io
  • Create a realm
  • Create an app

You will then see a client-id for your new app. We will use this shortly.

Using the data within our Next.JS site

First, install the TinaCMS dependencies:

npm install tinacms styled-components

or

yarn add tinacms styled-components

In your site root, add TinaCMS & register the ForestryClient like so:

_app.tsx

import React from "react";
import { withTina } from "tinacms";
import { ForestryClient } from "@forestryio/client";
import config from "../.forestry/config";

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default withTina(MyApp, {
  apis: {
    forestry: new ForestryClient({
      realm: "your-realm-name", // this was set by you in the previous step
      clientId: "your-client-id", // this is visible in your Tina.io dashboard
      redirectURI: "your webpage url", //e.g http://localhost:3000
      // identityProxy: "", // we can use an identity proxy if we want to use a CSRF token (see token storage below)
      // customAPI: "", // might be used with the identityProxy, to proxy through a custom backend service.
      // tokenStorage: (Default Memory). Possible values: "MEMORY" | "LOCAL_STORAGE" | "CUSTOM".
      // NOTE: If you choose to use LOCAL_STORAGE, you may be prone to CSRF vulnerabilities.
      // getTokenFn: undefined, // This is only used when "tokenStorage" is set to "CUSTOM". Instead of grabbing the token from local storage, we can specify how its access token is retreived. You might want to use this if you are fetching content server-side.
    }),
  },
  sidebar: true,
});

We'll also want to wrap our main layout in the TinacmsForestryProvider to support authentication

//...

function MyApp({ Component, pageProps }) {

  const forestryClient = useCMS().api.forestry

  return (<TinacmsForestryProvider
    onLogin={(token: string) => {
      const headers = new Headers()

      //TODO - the token should could as a param from onLogin
      headers.append('Authorization', 'Bearer ' + token)
      fetch('/api/preview', {
        method: 'POST',
        headers: headers,
      }).then(() => {
        window.location.href = '/'
      })

    }}
    onLogout={() => {console.log('exit edit mode')}}
  ><Component {...pageProps} />)
}

//...

This Next implementation relies on a backend function to save its auth details.

// /pages/api/preview.ts
import Cookies from "cookies";

const preview = (req: any, res: any) => {
  const token = (req.headers["authorization"] || "").split(" ")[1] || null;

  res.setPreviewData({});

  const cookies = new Cookies(req, res);
  cookies.set("tinaio_token", token, {
    httpOnly: true,
  });

  res.end("Preview mode enabled");
};

export default preview;

The last step is to add a way for the user to enter edit-mode. Let's create a /login page.

// /pages/login.tsx
import { useCMS } from "tinacms";

export default function Login(props) {
  const cms = useCMS();

  return (
    <button onClick={() => cms.toggle()}>
      {cms.enabled ? "Exit Edit Mode" : "Edit This Site"}
    </button>
  );
}

Your users should at this point be able to login and view their content from Tina.io's API. We will also want the site to build outside of edit-mode, for your production content.

Creating a local GraphQL server

Now that we've defined our schema, let's use the CLI to setup a GraphQL server for our site to use locally, or during production builds.

Start your local GraphQL server by running:

npx tina-gql server:start

or

yarn tina-gql server:start

pages/posts/welcome.tsx

import config from "../../.forestry/config";
import query from "../../.forestry/query";
import Cookies from 'cookies'
import { usePlugin } from "tinacms";
import {
  useForestryForm,
  ForestryClient,
  DEFAULT_LOCAL_TINA_GQL_SERVER_URL,
} from "@forestryio/client";

// These are your generated types from CLI
import { DocumentUnion, Query } from "../../.tina/types";

export async function getServerProps({ params }) {
  const path = `_posts/welcome.md`;

  const cookies = new Cookies(props.req, props.res)
  const authToken = cookies.get('tinaio_token')

  const client = new ForestryClient({
    realm: "your-realm-name", // this was set by you in the previous step
    clientId: "your-client-id", // this is visible in your Tina.io dashboard
    redirectURI: "your webpage url", //e.g http://localhost:3000
    customAPI: preview ? undefined : DEFAULT_LOCAL_TINA_GQL_SERVER_URL,
    tokenStorage: "CUSTOM"
    getTokenFn: () => authToken //supply our own function to just return the token
  });
  const content = await client.getContentForSection({
    relativePath: path,
    section: 'posts'
  });

  return { props: content };
}

export default function Home(props) {
  const [formData, form] = useForestryForm<Query, DocumentUnion>(props).data;
  usePlugin(form);

  return (
    <div>
      <h1>{formData.data.title}</h1>
    </div>
  );
}

Now, if you navigate to /posts/welcome you should see your production content. Once you log-in, you should also be able to update your content using the TinaCMS sidebar.

Next steps:

  • Make changes to our data-model, and verify our templates with $ tina-gql schema:audit
  • Setup typescript types for your data-model

Token storage

There are a few ways to store the authentication token:

Local storage (Default)

Storing tokens in browser local storage persists the user session between refreshes & across browser tabs. One thing to note is; if an attacker is able to inject code in your site using a cross-site scripting (XSS) attack, your token would be vulernable. To add extra security, a CSRF token can be implemented by using a proxy.

Within your client instantiation:

new ForestryClient({
  // ...
  identityProxy: "/api/auth/token",
});

From your site's server (This example uses NextJS's API functions)

// pages/api/auth/token

// ... Example coming soon

In memory (Coming soon)

This is our recommended token storage mechanism if possible. Storing tokens in memory means that the user session will not be persisted between refreshes or across browser tabs. This approach does not require a server to handle auth, and is the least vulernable to attacks.

Typescript

We can automatically generate TypeScript types based on your schema by running the following command with the Tina Cloud CLI:

yarn tina-gql schema:types

or

yarn tina-gql schema:gen-query --typescript

This will create a file at .forestry/types.ts.

Readme

Keywords

none

Package Sidebar

Install

npm i @forestryio/client

Weekly Downloads

36

Version

0.1.35

License

none

Unpacked Size

45 kB

Total Files

4

Last publish

Collaborators

  • warwick_tinacms
  • scottgallant