Platrium Docs

REST API Definitions

A deep dive into exactly how our REST API pipeline compiles code under the hood.

Platrium REST APIs are strictly design-first to be extendable, maintainble and enterprise-grade. Therefore, instead of writing code and hoping the documentation matches, or writing massive OpenAPI YAML files by hand, we use TypeSpec.

Why TypeSpec?

TypeSpec is a language created by Microsoft that feels exactly like modern TypeScript, but is purpose-built for defining APIs.

By writing our definitions in TypeSpec (.tsp files), we get:

  • Incredible DX: Syntax highlighting, linting, and a familiar TypeScript-like feel.
  • Reusability: We can define standard error models and pagination interfaces once, and reuse them across hundreds of endpoints.
  • Automation: Our Nx pipeline automatically compiles the TypeSpec into a massive openapi.yaml file, which is then fed into our generators to build the Go server stubs and Rust SDK client.

Folder Structure

All of our REST API definitions are centralized within the api/rest project.

openapi.yaml
errors.tsp
files.tsp
main.tsp

When compiled, the definitions in src generate the single openapi.yaml source of truth. Here is a visual representation of how that definition then flows through the rest of the platform:

Nx Build oapi-codegen openapi-generator UniFFI & WASM TypeSpec Definitions OpenAPI YAML Platrium Server (Go Backend) Generated Rust Bindings Cross-Platform Rust SDK Cross-Platform SDK Libraries

Compiling TypeSpec (api-rest)

Our API definitions live in the api/rest/src directory. When you run nx build api-rest, Nx executes the Microsoft tsp compiler.

Internally, this simply runs tsp compile src/ which gathers all the .tsp files and generates a monolithic standard openapi.yaml file located in api/rest/_generated/openapi.yaml. This YAML file acts as the single source of truth for the rest of the generation pipeline.

Generating the Go Backend (core-engine)

The backend logic lives in the core/ directory. Before Nx even attempts to build the Go binary, it triggers the _generate_restapi_bindings target.

This target runs oapi-codegen to translate the OpenAPI YAML into strongly-typed Go structs and interfaces for the Chi router:

go tool oapi-codegen -generate types -package restapi -o internal/restapi/types.gen.go ../api/rest/_generated/openapi.yaml
go tool oapi-codegen -generate chi-server,strict-server -package restapi -o internal/restapi/server.gen.go ../api/rest/_generated/openapi.yaml
go tool oapi-codegen -generate spec -package restapi -o internal/restapi/spec.gen.go ../api/rest/_generated/openapi.yaml

If your implementation in core/ no longer satisfies the generated interfaces, the subsequent go build step will fail immediately.

Generating the Rust SDK (sdk)

The Rust client generation lives in the sdk/ directory. Because Java-based generators can be incredibly frustrating to set up cross-platform, we bypass local JRE dependencies completely by running the generation inside a Docker container!

When the _generate_restapi_bindings target runs for the SDK, Nx spins up the openapitools/openapi-generator-cli Docker image, mounts your local workspace, and generates the Rust bindings directly into sdk/_generated:

docker run --rm -v "$(pwd)":/local openapitools/openapi-generator-cli generate \
  -i /local/api/rest/_generated/openapi.yaml \
  -g rust \
  -o /local/sdk/_generated \
  --package-name platrium-restapi \
  --additional-properties=supportAsync=true,preferPassthroughParameters=true,reqwestDefaultFeatures=reqwest/rustls \
  --global-property apiDocs=false,modelDocs=false,apiTests=false,modelTests=false

Notice we explicitly swap out the default TLS library for rustls during generation! This ensures that our Rust SDK can be safely compiled into WASM later on without running into OpenSSL linking issues in the browser.

Once this generation is complete, developers can write high-level wrappers around the internal API to be exposed to the _ffi targets (Android, iOS, WASM).

Design Decisions

When architecting the API layer for Platrium, we had to make several crucial decisions about protocols and frameworks.

On this page