A programming language that helps you move TypeScript backends to Golang.
To accomplish this, Forst supports you in four key ways:
| Benefit | How it works |
|---|---|
| Go ecosystem | Compiles directly to Go. You get full access to the Go package ecosystem, standard library and build tools. |
| TypeScript integration | Generates native TS definitions and a client directly from your backend source. No need for intermediate languages like GraphQL or Protobuf just for type safety. |
| Node.js interop | Call existing Node.js code directly from Forst to keep your existing tools and libraries during migration. |
| Incremental migration | Allows you to move small parts of your code at a time while your existing codebase keeps running, so you can keep shipping during migration. |
Building backend APIs often forces a trade off between runtime performance and developer speed. Maintaining separate schema contracts leads to lots of glue code. Full backend rewrites into more performant languages are often impossible to do safely.
Forst exists to remove that friction. You can migrate incrementally or write completely new backends with native performance and an ergonomic DX by design.
Forst code builds on Go foundations. Here is how a handler evolves from a basic executable to full interop and type generation.
Forst files use the .ft extension and support standard Go source structure.
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}Constraints on types validate boundary data automatically. You declare structured domain errors with error and check invariants with ensure.
type PlaceOrderInput = {
stockKeepingUnit: String.Min(1).Max(64),
quantity: Int.Min(1).Max(99),
}
error InsufficientStock {
requested: Int,
available: Int,
}
func PlaceOrder(in: PlaceOrderInput, available: Int) Result(String, Error) {
ensure in.quantity is Max(available) or InsufficientStock({
requested: in.quantity,
available: available,
})
return "ORDER-123"
}Call standard Go packages or existing Go functions directly in Forst source.
import "fmt"
func PlaceOrder(in: PlaceOrderInput, available: Int) Result(String, Error) {
ensure in.quantity is Max(available) or InsufficientStock({
requested: in.quantity,
available: available,
})
orderID := fmt.Sprintf("ORDER-%d", 123)
return orderID
}Import existing JavaScript or TypeScript modules into Forst with import node during migration.
import node "./legacy/payment"
func PlaceOrder(in: PlaceOrderInput, available: Int) Result(String, Error) {
ensure in.quantity is Max(available) or InsufficientStock({
requested: in.quantity,
available: available,
})
receipt := payment.Charge({ amount: in.quantity * 10 })
return receipt.id
}Run forst generate to create typed client packages for your frontend. Callers receive end to end type safety without writing schema glue code.
import { $orders } from "@forst/gen/orders";
const order = await $orders.PlaceOrder({
stockKeepingUnit: "SKU-99",
quantity: 2,
});Enable Effect mode in ftconfig.json to emit native Effect services and layers.
import { Effect } from "effect";
import { $orders } from "@forst/gen/orders";
const program = Effect.gen(function* () {
const order = yield* $orders.PlaceOrder({
stockKeepingUnit: "SKU-99",
quantity: 2,
});
return order;
}).pipe(Effect.provide($orders.Default));
await Effect.runPromise(program);Highlights from the docs feature comparison.
| Capability | Forst |
|---|---|
| Structural typing | Built-in records, signatures, and is narrowing |
| Validation on types | Built-in field constraints; boundary runtime checks |
| Error handling | ensure, nominal error types, and Result; no exceptions |
| Mocking and DI | use / with providers; no external DI framework |
| Type narrowing | is / ensure and type guards |
| Goroutines | Native go and defer via Go output |
| Capability | Forst |
|---|---|
| Go module ecosystem | Import Go packages natively |
| JS / npm ecosystem | Call legacy JS/TS via import node |
| Shared server ↔ client types | forst generate from the same .ft source |
| Call backend from Node | Generated client and HTTP invoke |
| Incremental migration | Mix .ft, .go, and legacy code in one codebase |
See ROADMAP.md for experimental features and planned work.
See also PHILOSOPHY.md for what guides and motivates us.
| Package | Purpose |
|---|---|
@forst/cli |
Forst compiler in JS/TS projects |
@forst/sidecar |
Dev server and HTTP client during migration |
Install the compiler in a Node project:
npm i -D @forst/cli
npx forst version@forst/cli pulls the matching native binary from GitHub Releases.
Optional extension in packages/vscode-forst.
- Registers
.ftfiles in the editor - Diagnostics via the compiler HTTP LSP (
forst lsp)
See packages/vscode-forst/README.md for installation and troubleshooting.
On Debian or Ubuntu, install the compiler from GitHub Releases (pick amd64 or arm64):
wget https://github.com/forst-lang/forst/releases/download/vX.Y.Z/forst_X.Y.Z-1_amd64.deb
sudo apt install ./forst_X.Y.Z-1_amd64.deb
forst versionSee docs/installation.mdx for other install paths (npm, native binary, Docker).
You can generate TypeScript types and a small client from your Forst code so front ends or Node callers get the same shapes your server uses, without copying types by hand.
Run forst generate with a .ft file or a folder of .ft files; it writes a generated/ tree (declarations plus helpers) and a client/ stub you can wire to your app. The dev server can also expose types over HTTP while you iterate.
Our primary inspiration is TypeScript's structural type system and its enormous success in making JavaScript development more ergonomic, robust and gradually typeable. We aim to bring similar benefits to Go development, insofar as they are not already present.
We also draw inspiration from:
- Zod — constraints and shape guards as composable runtime checks on nested data.
- tRPC — one source of truth for API shapes, with TypeScript types and a small client generated from Forst (
forst generate,examples/client-integration/). - Go and Rust — errors as values and explicit control flow (
ensure…or …) instead of exceptions.