Generally, I prefer decoupling frontend and backend such that all they need to share is the GraphQL schema. This makes the interface explicit. But I’ve found two scenarios where sharing code outside of the GraphQL schema can be particularly powerful:
- Global enums like user roles and entity types
- Pure functions for things like money manipulation and qid parsing
While GraphQL does include enums, they are generated for use in code only in the context of queries and fragments. Some enums are useful outside those contexts. For example, safely generating a qid from a uuid in a path like
organizations/<uuid> or determining visible menu items based on a user’s roles.
For shared types qids and money, having a single set of well-tested pure function utilities can be nice to avoid duplicating code.
There are a few options for where isomorphic code lives in relation to the rest of the frontend and backend code.
- In a single directory in a monorepo.
- In its own repository, for example with three separate repositories:
- In a special directory within the
A related but distinct mechanism is how the code gets built into the deployed apps:
- Directly import files and incorporate them into the frontend and backend builds. This is possible with option 1 for frontend and backend and option 3 for the backend only.
isomorphicas an NPM package and depend on it from
- Copy the code verbatim wherever it’s needed.
I like having the code live in the backend repo and get built directly into the backend app. Then copy the isomorphic directory into the frontend repo.
This keeps the development loop tight on the backend (ons PR can update both the backend code and isomorphic at once) and minimizes the overhead of needing bump the version and
yarn install on every change.
Copying isomorphic code from backend to frontend
A simple script to copy from backend repo to frontend repo:
#!/bin/bash # Set globstar shopt -s globstar read -r -d '' header << EOM // DO NOT EDIT // This file was synced from backend/src/isomorphic. // Make any desired changes in the backend repo. // See http://go/isomorphic for details. EOM # Copy all the backend files to tmp. rm -rf backend/tmp/isomorphic mkdir -p backend/tmp mkdir -p backend/tmp/isomorphic cp -r backend/src/isomorphic backend/tmp # Within backend, uses globstar to search src/isomorphic and all subdirectories for any .ts files to iterate for i in backend/tmp/isomorphic/**/*.ts; # Check that $i is a file, then uses printf to add 3 predefined lines at the beginning of the file do [ -f "$i" ] && echo "$header" > backend/tmp/isomorphic-scratch.ts # Add the contents of the file to the scratch file. cat "$i" >> backend/tmp/isomorphic-scratch.ts # Write the file to tmp. mv backend/tmp/isomorphic-scratch.ts "$i" done # Copy all the tmp files to the frontend. rm -rf frontend/src/isomorphic cp -r backend/tmp/isomorphic frontend/src # Clean up. rm -rf backend/tmp/isomorphic rm -rf backend/tmp/isomorphic-scratch.ts
Inside the frontend
"copy-isomorphic": "cd .. && ./backend/scripts/copy_isomorphic_code.sh",
Syncing with Github actions
It’s possible to automate the copying of isomorphic code using a Github Action:
name: Isomorphic Sync on: workflow_dispatch: push: branches: - main paths: - 'src/isomorphic/**' concurrency: group: isomorphic-sync jobs: open-pr-for-schema: runs-on: ubuntu-latest steps: - name: Checkout Backend uses: firstname.lastname@example.org with: path: backend - name: Checkout Frontend uses: email@example.com with: path: frontend repository: foo/frontend # Using a Personal Access Token here is required to trigger workflows on our new commit. # The default GitHub token doesn't trigger any workflows. # See: https://github.community/t/push-from-action-does-not-trigger-subsequent-action/16854/2 token: $ - name: copy_isomorphic_code run: backend/scripts/copy_isomorphic_code.sh - name: Create Pull Request uses: firstname.lastname@example.org with: path: frontend token: $ commit-message: Sync src/isomorphic to $ committer: foo-bot <email@example.com> author: foo-bot <firstname.lastname@example.org> branch: foo-bot/isomorphic-sync delete-branch: true title: Sync src/isomorphic from $ body: | Copying isomorphic code from $/$/commit/$ labels: automated