Homepage platformOS Logo

Using REST Endpoints with a React App

Last edit: Nov 04, 2020

Problem

How to use platformOS as a REST backend for your React app? The goal is to easily define new endpoints for your React app and have your app ready for local development communication with your Instance via proxy.

This article is based on React version 17.

To follow this article, you should have your development environment and a platformOS Instance set up, be familiar with pages, layouts, and React.

Solution

Step 1: Create place for the React app files

The source files of a web app are not part of platformOS and can be stored anywhere you want.
In this example, to keep everything together in one repo and one project, you'll create the React app directly in your Instance's folder but not inside the /app folder. It is not recommended to deploy your source files to the server, only the built and minified ones.

Navigate to your Instance folder and create the new app with:

npx create-react-app react-app

Instead of react-app, you can also use another name for your project.

Now you have the /app, modules, and react-app folders in your local Instance.

Step 2: Create your React build as your platformOS page

You can find package.json under react-app. You need to add three new scripts:

  1. a layout script to copy the built index.html to your platformOS app's index page
  2. one to clear the assets folder
  3. and one to use the built folder as the assets

Note

This example is built on a Unix-based system. On Windows, you might need to install and use cpy and rimraf.

    "layout": "cp ./build/index.html ../app/views/pages/index.html.liquid",
    "assets:remove": "rm -rf ../app/assets/web-app",
    "assets:copy": "mv ./build/ ../app/assets/web-app"

You can now use these scripts in your build script:

"build": "react-scripts build && npm run layout && npm run assets:remove && npm run assets:copy"

Step 3: Set up your app's public folder

platformOS serves static assets from a CDN, so you need to set up your Instance's Public Folder with Environment Variables

For this you need to set up the PUBLIC_URL variable in your React app's .env file under the app's root folder:

PUBLIC_URL=https://uploads.staging.oregon.platform-os.com/instances/[INSTANCE ID]/assets/

How to get your-uploads-path?

The asset_url filter can be used to fetch the root folder for serving assets.


{{ "" | asset_url }}

It will be similar to:

https://uploads.staging.oregon.platform-os.com/instances/<YOUR-INSTANCE-ID>/assets/

We strongly recommend placing all generated assets in a subfolder, e.g., app/assets/web-app. Doing so ensures no future conflicts once additional, unrelated assets are added to the project (like graphic files used in emails or a separate admin panel app.)

To test it, build your React app and deploy your Instance.

/react-app$ npm run build
/$ pos-cli deploy dev

It should result in the default create-react-app page as your Instance's home page.

Step 4: Prepare your endpoints' headers

Your endpoints will serve content as JSON. You need to create a Liquid file under app/views/partials/shared/api/headers.liquid to render the necessary headers:


{
  "Content-Type": "application/json"
}

Step 5: Create your model's schema

You can define your data models under app/model_schemas/. In this example, you will store basic book infos so create app/model_schemas/book.yml:

name: book
properties:
  - name: title
    type: string
  - name: subtitle
    type: string

Step 6: Create a REST endpoint to store a new book

Create GraphQL mutations to store your data. Place these GraphQL files in the app/graphql/ folder.

To add a new book, create a new file app/graphql/books/create_book.graphql:

mutation($title: String!, $subtitle: String) {
  book: model_create(
    model: {
      model_schema_name: "book"
      properties: [
        { name: "title", value: $title }
        { name: "subtitle", value: $subtitle }
      ]
    }
  ) {
    id
  }
}

The next step is to create a new page used as a REST endpoint for your React application. We recommend storing all endpoints used in the API inside the app/views/pages/api folder to ensure separation from other site sections.

Additionally, when planning to use different request methods for one URL like POST /api/books, GET /api/books, etc., we suggest naming the files after the request method related to the given endpoint. Place the endpoint responsible for adding new books in app/views/pages/api/books/post.liquid.


---
slug: api/books
method: post
layout: ''
response_headers: >
  {% render 'shared/api/headers' %}
---

{% liquid
  graphql result = 'books/create_book', title: context.params.title, subtitle: context.params.subtitle
  unless result.book
    response_status 400
  endunless
%}

{{ result }}

You’ve defined the endpoint’s slug and method and removed the default layout. The previously created headers.liquid file is used to render the appropriate response header.

You’re calling the books/create_book GraphQL mutation using the request parameters. All GraphQL results always return valid JSON. On error, the result.book key will not be present, and you should add an appropriate error response status code. Finally, render the whole result object. It will contain either the new book id or an error message when creating the book has failed.

Step 7: Use your REST endpoint in your React app

The API endpoint is ready. The next step is to add a book form in the /react-app/src/App.jsx file. In this example, you will use react-hook-form to implement the POST request. In the end, the created book's ID will be logged to the JavaScript console.

We recommend using axios as it allows you to easily add the required csrf-token to every request.

axios.defaults.xsrfCookieName = 'CSRF-TOKEN';
axios.defaults.xsrfHeaderName = 'X-CSRF-Token';
/react-app$ npm install axios --save
import React from 'react';
import { useForm } from 'react-hook-form';
import axios from 'axios';

import logo from './logo.svg';
import './App.css';

axios.defaults.xsrfCookieName = 'CSRF-TOKEN';
axios.defaults.xsrfHeaderName = 'X-CSRF-Token';

function App() {
  const { register, handleSubmit, errors, formState } = useForm();

  async function onSubmit(data) {
    try {
      const response = await axios.post('/api/books', data);
      console.log(response);
    } catch (err) {
      console.error(err);
    }
  }

  return (
    <div>
      <h1>Save a new book</h1>

      <div>
        <form onSubmit={handleSubmit(onSubmit)}>
          <input
            name="title"
            placeholder="Title"
            ref={register({
              required: 'Required',
            })}
          />
          <label htmlFor="title">{errors.title && errors.title.message}</label>

          <input name="subtitle" placeholder="Subtitle" ref={register()} />
          <label htmlFor="subtitle">
            {errors.subtitle && errors.subtitle.message}
          </label>

          <button type="submit" disabled={formState.isSubmitting}>
            Save
          </button>
        </form>
      </div>
    </div>
  );
}

export default App;

Step 8: Set up proxy for local API calls during development

During development, your site will most likely be served from the localhost domain. You do not want to add CORS headers to our API and open it to external websites due to security concerns. To resolve this issue, you need to use a proxy. You can achieve that by installing http-proxy-middleware.

/react-app$ npm install http-proxy-middleware --save

Create react-app/src/setupProxy.js

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function (app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'https://your-platformos-instance.com/',
      secure: false,
      changeOrigin: true,
    })
  );
};

After that you can run your local environment with

/react-app$ npm start

and synchronize your platformOS files with

/$ pos-cli sync dev

Your Instance will now handle all API calls.

Contribute to this page

Github Icon

Questions?

We are always happy to help with any questions you may have.

contact us