Build API Driven Statically Generated Frontend Applications

Published in coding on Aug 8, 2020

Build API Driven Statically Generated Frontend Applications

Build API Driven frontend Featured Image

『This article is the first part of 2 parts blog series.』

As the new trend evolves to separate the backend and frontend of a web app, there is a need to keep both applications in sync.

For example, consider building an application where the user is allowed to choose one of the subscription plans you are offering. Also, you decided to have all subscriptions maintained on Stripe, which means you are managing the information about the different plans on both the backend and frontend.

The easiest option is to have a separate configuration for the backend and frontend, which could work for an MVP and initial launch. As it's an MVP and initial launch, you might offer a discounted plan for a limited time as a launch promotion. But after three months, you decided you want to offer only the full pricing, do you see it now? You have to bring both of you frontend and backend engineers, and to ask them to change the configuration for both applications, run testing for each subscription plan to ensure everything works.

Would it not make more sense to have a single configuration repository for both the frontend and backend? So you can change the configuration in one place, and you are sure everything will continue to work as expected. Let's dive into how to achieve that quickly.

I will be using Next.JS as the frontend framework and Laravel as my backend. But you are free to choose any backend other than Laravel. Next.JS offers two great functions getStaticProps and getServerSideProps that will make the whole process so easy.

Prerequisites

To build this sample applications, I will be using the following tools:

  1. Yarn & PHP composer as package managers
  2. Next.JS (for the frontend)
  3. Laravel (for the backend)
  4. Laravel Valet (to serve the backend)

Once I complete the application development, I will be using:

  1. Vercel (to deploy my frontend to production)
  2. Vapor (to deploy my backend to production)

Table of Contents

  1. When to use this technique
  2. Prepare your backend
  3. Build your front end
  4. Deploy your application
  5. Deployment web-hooks FTW.

1. When to use this technique

This technique will allow you to keep both your backend and frontend in sync. You might consider this technique if you have a configuration on both the frontend and backend that is frequently changing. Few examples to consider:

  1. Subscription plans and their configuration
  2. Dropdown menus that represent specific values in the database
  3. Drive the frontend configuration from the database; this is even a more generic approach, think of it as running a headless CMS but for your configuration. Where you can configure your frontend through your admin dashboard. As we will go through the process of setting up your application, you will notice that the overhead of this approach in terms of coding and maintenance is considered minimal, but the advantages are considerable. So, you can leverage this concept in your application as long as you have a backend already in place.

For this article, I will be building an application on Next.js and Laravel PHP, where the users can subscribe to a service using the Stripe Checkout page. It is a straightforward application but will require both the frontend and backend to have the same knowledge of the Stripe plan id.

Disclaimer: The full Stripe flow will usually consist of initiating a Checkout session on the backend, forward a token to the frontend. And after the user completes the Checkout session, the backend should be listening to webhooks to commit the changes on the application side. For this article, I will focus on the initial step only where the user clicks a link, then a Checkout session is created, and the user is redirected to the Stripe Checkout page.

2. Prepare your backend

And backend technology can work in that scenario. In my situation here, I will work with Laravel PHP as this is my platform of choice, but again, the same setup can run by any technology. You can even use Next.js API functionality.

As the focus is not on the backend here, I will not be jumping into the details of how to install a Laravel application. So I will consider you have a backend up and running.

We will be focusing on how the data is stored and retrieved from the backend. I usually prefer to have this data stored in a database where it can be quickly accessed and updated in the future. This way, it becomes straightforward to manage this configuration from any admin dashboard you have. So back to our example, I will have a table in the database that holds all the information related to subscription plans. My advice here is to add as many data points as you can. So add details like Subscription/Plan name, price (monthly/annually), Stripe plan_id, and description. The goal here is to add all the information that you will need in both the backend and the frontend. In my case, I will build a migration that looks like that:

public function up()
{
    Schema::create('subscription_plans', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('stripe_plan_id');
        $table->decimal('monthly_price');
        $table->decimal('annual_price');
        $table->text('description')->nullable();
        $table->timestamps();
    });
}

The next step is to build an endpoint to leverage this data. First, I create a route in api.php, which is very Laravel specific, but you will get the idea.

Route::get('plans', 'GetPlansController@list');

Then the controller:

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class GetPlansController extends Controller
{
	public function list()
	{
    	return Plan::all();
	}
}

The above code, for sure, is an oversimplified backend code; probably in real production code, you will need to add some caching and protect your endpoint through some token and throttling.

That, in essence, is everything you need to prepare your backend, the next step is to focus on the frontend.

3. Build your Frontend

For the frontend, I will be using Next.js, mainly because it offers excellent features to run code during the build time or to run on the server-side before hitting the client in SSR applications.

The main feature we will be focusing on is getStaticProps, which is a pre-rendering data fetching strategy. In simple words, it's a function that is called during the build process, and the output of that function feeds into the page as props. Let's see how it works for our example.

『For completeness, if your application has SSR functionality, you need to use getServerSideProps instead. It has the same pre-rendering fetching capabilities, but the difference is that it runs on the server-side with every page view.

In our application, we need to have a billing page that displays the different plans, and the user can choose one of the plans to subscribe.

The page will display basic information to the user: plan price, plan description, and a link that forwards the user to the Stripe Checkout page, so it requires some sort of plan unique ID to communicate to the backend. The page will look like that:

Service Billing Preview

And the code for this page will look like that:

import Head from 'next/head'
import dynamic from 'next/dynamic'


export default function Home() {
  const PlanCard = dynamic(()=>import("../components/plan-card"),{ssr: false})
  return (
    <div className="container">
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <div className="mt-12 text-center w-full">
          <h1 className="title text-4xl">
            Service Billing
          </h1>
        </div>

        <p className="description text-center text-2xl">
          Choose one of the below subscriptions.
        </p>

        <div className="grid grid-flow-rows grid-cols-2 mx-auto w-1/2 gap-4">
          <PlanCard plan={{
            name: "Plan 1",
            description: "Some Description here",
            price: 15
          }} />

          <PlanCard plan={{
            name: "Plan 2",
            description: "More Description here",
            price: 25
          }} />

        </div>
      </main>

    </div>
  )
}

And the code for PlanCard

  export default ({plan}) => {
  return (
    <div className="border rounded-lg p-8 mt-6">
      <h3 className="text-xl text-gray-700 font-medium">{plan.name}</h3>
      <p className="text-md text-gray-600">{plan.description}</p>
      <div className="flex">
        <span className="text-2xl text-gray-900 font-semibold flex-grow">${plan.price}</span>
        <span className="text-3xl text-gray-900 font-semibold flex-grow-0"> &rarr; </span>
      </div>
    </div>
  )
}

As you can see, for now, we are loading test values for the different plans. Let's fetch the values from the backend.

First, we need a library to connect to the API over HTTP. There are few alternatives, and it's up to you to choose the one that works for your application. I will be using Axios(https://github.com/axios/axios "Axios"). It's a complete HTTP client for your node.js or JS frontend application.

To install axios in your application, just add it:

yarn add axios

I usually build a wrapper library class to make all the HTTP functionality easy. The wrapper library will handle all requirements related to authentication with the backend. Even though we do not have any specific authentication requirements for this demo app, I will use the approach. The api.js wrapper library will look like that.

import axios from "axios"

export default class Api {

  static get =  async (uri) => {
    return await axios.get(${this.baseUrl}${uri}, this.opts)
  }

}
Api.baseUrl = process.env.API_URL
Api.opts = {
  headers: {
    'X-Requested-With': 'XMLHttpRequest',
    'Content-Type': 'application/json',
  }
}

Now to the most crucial part, where we call the API during the build time to capture the information and make it available to the frontend application. We will add a new async function to the billing page.

The getStaticProps function has a couple of magical features:

  • It runs only on the server-side, so if you call the API using any specific API keys, those will be secured.
  • Any import statements that are using in the getStaticProps will not be bundled into the client scripts.

Here is the code for getStaticProps:

import Api from '../lib/api'
export async function getStaticProps() {
  let plans = {};
  await Api.get('api/plans').then(data => {
    plans = data.data;
  });

  return {
    props: {
      plans,
    },
  }
}

Now rewrite the main component in order to import the props from getStaticProps:

export default function Home({plans}) {
  const PlanCard = dynamic(()=>import("../components/plan-card"),{ssr: false})
  return (
    <div className="container">
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <div className="mt-12 text-center w-full">
          <h1 className="title text-4xl">
            Service Billing
          </h1>
        </div>

        <p className="description text-center text-2xl">
          Choose one of the below subscriptions.
        </p>

        <div className="grid grid-flow-rows grid-cols-2 mx-auto w-1/2 gap-4">

          {
            plans && plans.map(plan => (
              <PlanCard plan={plan} />
            ))
          }


        </div>
      </main>

    </div>
  )
}

This code now basically iterates over the array of plans that were imported from the API and displayed them to the user.

How getStaticProps actually works

Behind the scenes and during the build time, the getStaticProps will call the API and stores the response in JSON files and make them available to the react app with each client call. All future requests from the client will not trigger the API, statically served and blazingly fast.

Conclusion

Now we were introduced to the concept of API Driven Frontend applications, the next step is to deploy it to the cloud. This will be the focus of the second part of this series.