Juha-Matti Santala
Community Builder. Dreamer. Adventurer.

Simple backend system for frontend workshops in Javascript

I coach in many programming workshops. If you have coached in one too, you might have noticed that it's challenging to find the right balance between depth and width. Especially in frontend workshops, I don't want to confuse students with building backends or API systems but I do want to provide them easy to use and extendable system.

I built a starter kit based on create-react-app that doesn't touch the basic React side of the frontend but adds a backend, couple of commands and an API client so that the user doesn't have to worry about doing HTTP calls themselves.

You can find my starter kit code from Hamatti/cra-with-json-server

My workshop philosophy

There are many ways to teach. Some take a theory-first approach and only advance to next thing when something has been sufficiently learned. It's a great approach for long-term learning but in short workshops (like a two-day workshop over a weekend), you end up not getting much done.

I like the way Rails Girls does it and my philosophy has been very much influenced by it. You get a lot of things done by taking advance of pre-built stuff. The point of the workshop is not to master things but to get interested, see all the different parts of building a project and deploying something to cloud to show to your friends.

When you then want to learn more, you can come back to certain steps and learn more about that.

Backend Storage

Luckily, a friend of mine, Johannes, introduced me to json-server. It's a wonderful npm package that provides you with HTTP endpoints into data stored in a JSON file.

json-server is a tool that provides a lot more than what we're gonna go through today. It's great for prototyping and mocking APIs with a huge offering of endpoints. Today, we are mainly interested in GET, POST, PATCH and DELETE endpoints.

As mentioned earlier, my first approach (I'm hoping to make it easier to plug in to any system later) was using create-react-app, so if you want to follow along, create a new app with

create-react-app my-new-project

and install json-server

npm install -g json-server

Next we create a JSON storage into backend/db.json. I also added a backend/readme.md that explains how to use it. Your backend/db.json can either start as an empty JSON

{}

or if want to set it up for a tutorial, you can pre-populate it with collections you want. A collection in this case is just a key with an array of objects.

{
  "authors": [
    {
      "name": "Juhis",
      "username": "hamatti",
      "email": "juhamattisantala@gmail.com"
    }
  ]
}

If you then run

json-server backend/db.json -p 3001

you will gain API access into http://localhost:3001. You can try this by running a curl in a new terminal tab to fetch data:

curl http://localhost:3001/authors

You should see

[
  {
    "name": "Juhis",
    "username": "hamatti",
    "email": "juhamattisantala@gmail.com"
  }
]

in your terminal.

To make it simpler, I added a npm script to package.json to run it.

"scripts": {
    "backend": "json-server ./backend/db.json -p 3001"
}

Now you'll be able to start the backend API with npm run backend and you don't have to know what happens behind the scenes.

API

In src/api/apiClient.js, I've created basic getters and setters to access the data in API:

import axios from "axios";
​
const config = {
  baseURL: "http://localhost:3001",
  headers: {
    "Content-Type": "application/json",
  },
};
​
const client = axios.create(config);

I'm using axios for calls and create a client with the configuration. After the setup, I have these functions for interacting with the API.

export function getAuthors(ctx) {
  return client.get("/authors").then(({ data }) => {
    ctx.setState({
      authors: data,
    });
  });
}
​
export function updateAuthors(id, data) {
  return client.patch(`/authors/${id}`, data);
}
​
export function createAuthors(data) {
  return client.post(`/authors`, data);
}

With this setup, an user should never have to touch backend/ folder nor the src/api/ folder, they just need to import them where they wish to query the API.

The first function, getAuthors is currently very React specific: it expects a parameter to be this of a stateful React component and it saves the data directly into the state.

Build new endpoints

A basic workshop can be done with just pre-defined data structure and endpoints but it's most fun when people can decide themselves what data to add.

So let's add a functionality to create that on the fly. This script is currently not the most error-proof: if you run it twice with the same input, it will break.

In package.json, I added a new script:

"scripts": {
    "generateAPI": "node generateAPI.js"
}

and the file generateAPI.js:

"use strict";
​
const fs = require("fs");
const DB_PATH = "backend/db.json";
const API_CLIENT_PATH = "src/api/apiClient.js";
​
let key = process.argv[2];
if (!key) return;
key = key.toLowerCase();
​
let originalData = JSON.parse(fs.readFileSync(DB_PATH));
originalData[key] = [];
​
// Write to file
fs.writeFileSync(DB_PATH, JSON.stringify(originalData));
​
const titleCase = `${key.charAt(0).toUpperCase()}${key.substr(1)}`;
​
const newFunctions = `export function get${titleCase}(ctx) {
    return client.get("/${key}").then(({ data }) => {
      ctx.setState({
        ${key}: data
      });
    });
  }
​
​
  export function update${titleCase}(id, data) {
    return client.patch(\`/${key}/\${id}\`, data);
  }
​
​
  export function create${titleCase}(data) {
    return client.post("/${key}/", data);
  }`;
​
const originalApiClient = fs.readFileSync(API_CLIENT_PATH);
​
const newApiClient = `${originalApiClient}\n\n${newFunctions}`;
​
fs.writeFileSync(API_CLIENT_PATH, newApiClient);
​
console.log(`${key} was added into the database and API.
​
You can now import new functions with
​
    import { get${titleCase}, update${titleCase} and create${titleCase} } from './api/apiClient';
​
and you will have access to the data.`);

You run it with npm run generateAPI [model] and it adds model into backend/db.json as a "model": [] collection as well as generates get[Model], update[Model] and create[Model] functions into src/api/apiClient.js.

It's a rather hacky system for now but with my testing, it works quite nicely. With a npm script command, you can add new collections into your storage and just import the new functionality from apiClient.js.