Skip to content

Latest commit

 

History

History
191 lines (159 loc) · 8.25 KB

File metadata and controls

191 lines (159 loc) · 8.25 KB
title Why Forst?
description Design priorities, what you get from Forst, and what the language omits.

While backend engineering covers a wide surface, some central issues affect everyone:

  • Developer experience: everyday work needs fast feedback and low boilerplate.
  • Performance: prod needs to keep up when traffic spikes and complexity grows.
  • Contracts: boundaries need to validate bad data before it reaches domain logic.

Forst is for teams who want all three: one .ft source that ships as native Go while allowing incremental migration within your existing TS or Go codebase.

Keeps client-side boilerplate low and makes working with complex data easy. Compiles fast, ships static binaries, and scales in production.

The analogy

Similar to TypeScript, Forst allows working within your existing codebase and replacing more and more code over time:

flowchart TB
  subgraph jsStack [JavaScript ecosystem]
    JS[JavaScript]
    TS[TypeScript]
    JS --> TS
  end

  subgraph goStack [Go ecosystem]
    Go[Go]
    Forst[Forst]
    Go --> Forst
  end

  TS -.->|"types + ergonomics"| JS
  Forst -.->|"types + ergonomics"| Go
Loading

TypeScript added types on top of JavaScript while keeping the same runtime. Forst does the same on both sides of your stack:

  • Go: Ship native binaries with go build and typed boundaries layered on top. See Go interop.
  • Node: Stay type-aligned with the frontend and migrate route by route without a big bang rewrite. See JavaScript interop.

Feature comparison

Compare Forst to TypeScript and Go.

Compatibility

How each stack talks to other languages and adopts code incrementally.

Capability TypeScript Go Forst
Incremental migration Adopt TS file by file Mix packages over time Mix .ft, .go, and legacy JS/TS
Go module ecosystem No (WASM only) Native Import Go packages
JS / npm ecosystem Native No Call legacy JS/TS via import "./path" js
Call backend from Node Same runtime No Generated client + HTTP invoke
Shared types (server ↔ client) Straightforward Manual / separate forst generate
OpenAPI, RPC, and route files Separate codegen from schemas go generate Plugins emit them from your .ft backend

Language

How each language models data, errors, and concurrency.

Capability TypeScript Go Forst
Static typing Yes Yes Yes
Structural typing Built-in Structs (nominal) Built-in (flexible)
Mocking & DI DI libraries Manual / Fx / Wire use / with
Validation on types Schema library (Zod, etc.) Manual checks Built-in constraints
Error handling Exceptions (try/catch) (T, error) values ensure, Result, nominal errors`
Type narrowing Discriminated unions, type guards, asserts Type assertions is / ensure, type guards
Exceptions Yes (try/catch) Yes (panic/recover) No (Go panics possible)
User-defined generics Yes Yes No (built-ins, planned)
Class inheritance Yes No (composition) No (shapes)
Goroutines Via async/await Native (go / defer) Native (go / defer)
switch / case Yes Native Yes (Go-faithful; type switches omitted)
select / channels No Native No (planned)
Runtime reflection Limited Native (reflect) No (compile-time focus)

Infrastructure

How you ship, debug, and adopt each stack day to day.

Capability TypeScript Go Forst
Production runtime V8, JavaScriptCore Native Native Go (compiled from .ft)
Execution model JIT Native Native (via Go)
Static binary deployment No Yes Yes (via Go output)
Language server (LSP) Yes Yes (gopls) Yes (forst lsp)
Open source Yes Yes Yes
Go import loading, JS bridge interop, Providers, generate plugins, and several invoke paths are **experimental**. See the [roadmap](/resources/roadmap) for current status.

What you get

Typed records with field access, signatures, and **`is`** narrowing. You define one schema layer for the server. Constraints on fields. The compiler checks known values at compile time and emits runtime checks at the boundary. **`forst generate`** emits **`.d.ts`** from the same **`.ft`** definitions the server uses. Optional [plugins](/workflow/plugins) emit schemas, RPC contracts, and routes. Readable Go output. Ship with **`go build`**, existing modules, tests, and CI. **`ensure`**, nominal **`error`** types, and **`Result`**. Failures are values you handle in control flow. Mix **`.ft`** with **`.go`**. Migrate route by route with **`forst dev`** or **`@forst/sidecar`**.

Design priorities

Shape matters more than inheritance. Types and runtime checks run before your logic. Require explicit annotations where inference would lie. Inference applies only in clear cases. Import Go packages. Deploy with `go build`. Export types and generate contracts when full stack teams need shared shapes.

What Forst omits

No `try` / `catch` / `throw`. Failures are values you handle or explicitly ignore. `ensure` signals intent. Inheritance trees obscure which fields an API actually has. Control flow changes use ordinary keywords you can read top to bottom. Wiring and validation happen at compile time instead of through introspection. Types cannot depend on runtime values. Conversions stay visible because backend data integrity matters. Panics may appear in generated Go or third-party libraries. Forst itself favors `Result` and Go's error returns.

Read more

How the language feels day to day. What exists, what's experimental, what's planned. Install and run your first handler. Long-form design doc in the repository.