Contract-First
Single source of truth — define your API contract once, get typed server, client, and tests automatically.
One contract. Typed server, typed tests, typed client. Zero magic.

Define once, implement, test — three files and you have a fully typed API.
Single source of truth — Typia-speed validation, no compiler plugin, no build step.
// api/src/api/ItemApi.ts
// Standard schema definitions.
export const IsItem = IsObject({
id: IsNumber,
title: IsString
})
export const IsCreateItemRequest = IsObject({
title: IsString
})
// Define custom errors with typed data.
const OUT_OF_STOCK = GGerror.define("OUT_OF_STOCK", IsObject({
amountLeft: IsNumber
}));
// Define your contract - think about those like a function signatures.
const ItemApiContract = new GGContractClass("ItemApi", {
list: {
success: IsArray(IsItem),
errors: [SERVER_ERROR, OUT_OF_STOCK] // We say what errors our contract can return.
},
create: {
input: IsCreateItemRequest,
success: IsItem,
errors: [VALIDATION_ERROR, SERVER_ERROR]
}
})
export const ItemApi = httpSchema(ItemApiContract)
.pathPrefix("api/items")
.routes({
list: GGRpc.GET("list"),
create: GGRpc.POST("create")
})One line to bind your contract. Your Runtime is the bootstrap — all wiring visible in one place.
// server/src/AppRuntime.ts
export class AppRuntime extends GGRuntime {
public static readonly NAME = "app"
protected compose(): void {
new GGHttp().http(ItemApi, new ItemApiImpl())
}
}
// Simple implementation examples
type ItemApiContract = GGContractImplementation<typeof ItemApiContract.methods>;
export class ItemApiImpl implements ItemApiContract {
private readonly geocoder = new GeocodingService();
public list = async (): Promise<Item[]> => {
// ...
}
public create = async (input: typeof IsCreateItemRequest.infer): Promise<Item> => {
// ... Also use geocoder here ...
throw OUT_OF_STOCK({amountLeft: 10}) // throw errors.
}
}
@mockable // This enables mocking/syping in tests for internal classes.
export class GeocodingService {
async resolve(address: string): Promise<LatLng> {
return await this.client.geocode(address)
}
}tsx src/AppRuntime.ts # That's it. Service is running.
# Launch more to get load balanced multi-instance local setup...Real workers, real ports, per-request mocks. Each test gets its own isolated runtime.
// server/test/item.test.ts
describe("Item API", () => {
GGTest.startWorker([AppRuntime, AppRuntime]) // Start as many as you want, also different services etc.
// Yes, it is real worker! Can also launch startInline for fastest test run speed.
// Yes, you can get full coverage including integration tests.
// You can mock outbound service calls. If outbound service exists, calls go through.
// No DI - your Runtime is the bootstrap! No more duplicating whole wiring in tests.
const myApis = new TestContext("Items")
.apis({
item: ItemApi
// Add API-s you are going to call in the tests.
// Can be many, different services/runtimes etc. Doesn't matter.
})
test("create and list items", async () => {
// Call your API-s as you normally would in clients.
await myApis.item.create({title: "Buy groceries"})
.toMatchObject({id: 1, title: "Buy groceries"})
// Anything vitest supports, you can still do - even snapshots.
const result = await myApis.item.list()
expect(result).toMatchSnapshot()
// Can even mock random internal classes running within your service.
await myApis.item.create({title: "Visit Times Square"})
.with(
mockOf(GeocodingService).resolve // Mock applies only during this request, nicely scoped!
.toEqual({address: "Times Square, NYC"})
.andReturn({lat: 40.758, lng: -73.985})
)
.toMatchObject({title: "Visit Times Square", lat: 40.758})
})
})vitest # Each test suite gets its own runtime with isolated ports.Copy the starter service to get going quickly.
# Start with a simple starter template
npm create @grest-ts/starter my-app
# Terminal 1 — server
cd server && npm run dev
# Terminal 2 — client
cd client && npm run devEverything is wired up — API contract, server handler, integration test, and a client that calls the API. Build on it.
Even your AI buddy is going to enjoy it!