In this post, I'll introduce the first component of the Rainbow Platform. If you’ve just found this article, check out the original idea explained here. If you're here for the code, you can access it on mjsdevs/rainbow on GitHub.
To begin, it's worth taking a look at the first draft for Gaia. Just to recap from the last post, it will have three major components:
-
Console –
gaia.console
: the "AWS Console"-like front-end, built with React and TypeScript. -
Registry –
gaia.registry
: the function registry, acting as a custom package registry (as the name suggests). -
Proxy –
gaia.proxy
: the gateway for active functions (more on this concept will be covered later).
Gaia will be built with TypeScript and tRPC, following the microkernel architecture. Given this choice, it's natural to use a single monorepo for the UI, BFF, and all the modules as well.
Gaia Console Web
To build the admin, I’ve chosen to use a standalone React SPA. Since Gaia modules will be accessible through different clients (CLI, console, chatbot, etc.), each client must implement its own BFF, also using tRPC.
This is the first mockup of the admin page, which currently includes the only available feature: managing the function registry.
Why tRPC?
I’ve used both GraphQL and REST in the past. From json:api to Relay, each approach for building APIs has its pros and cons. However, a constant challenge is choosing between code-first and schema-first approaches.
Take GraphQL as an example—REST APIs have a similar challenge, often using something like OpenAPI as the "schema." If you want your code to be the source of truth, you can use something like TypeGraphQL, which generates the GraphQL schema based on decorators in your classes. On the other hand, you can use the reference GraphQL server implementation (graphql-js
) with raw Schema Definition Files as your contract—then generate TypeScript types using something like GraphQL Codegen.
In both scenarios, you have to add complexity to your toolchain by "extracting" types. In other words, you either generate interfaces from your schema or build a dynamic schema (a language-agnostic interface) from your code.
This is fine if you have a Java backend and an iOS app, but with TypeScript, the goal is to share the same interfaces and types between front-end and back-end. So why not use the language itself? That's the use case for tRPC.
The general idea of using tRPC is simple: build your server with the @trpc/server
package, export the TypeScript interface to the front-end application, and consume it using @trpc/client
. No schema or JSON format conventions—you just focus on the code, and it integrates through the language itself. In this case, where we are building both the web application and its BFF using a monorepo structure, tRPC is the perfect fit.
Using tRPC feels like a hybrid approach, combining the best of both GraphQL (with queries and mutations) and REST concepts. It also significantly improves developer experience (DX) for full-stack development.
Monorepo
The monorepo structure is another topic that could fill an entire article, but I’ll give a brief explanation.
Instead of having one repository per project, we use a single repository for all the source code. This choice leads to some interesting questions:
- How do you manage dependencies and build workflows?
- What can be reused between projects (if anything)?
For dependency and workflow management in Gaia, we can use any tool available in the Node ecosystem. You might suggest Lerna or even Turborepo. For our needs, the default npm workspaces will suffice.
As for reusability, it mainly involves using tRPC across all components.
In the upcoming posts, I'll dive into the front-end application itself (gaia.console.web
). Spoiler: it will be based on Cloudscape, the design system currently used by AWS.
Top comments (0)