# Example Usage of typescript-go with a Simple Go API

> Discover example usage of typescript-go to integrate the TypeScript compiler into your Go API. Connect via pipe transport to query symbols, types, and diagnostics programmatically.

- Repository: [Microsoft/typescript-go](https://github.com/microsoft/typescript-go)
- Tags: getting-started
- Published: 2026-04-25

---

**You can integrate the TypeScript compiler into a Go application by launching `tsgo --api` to start a pipe-based server, then connecting via `api.NewPipeTransport` and `api.NewSession` to programmatically query symbols, types, and diagnostics.**

The `microsoft/typescript-go` repository is a native Go port of the TypeScript compiler that exposes a programmatic API for embedding type-checking capabilities directly into Go programs. Unlike the traditional Node.js-based `tsc`, this implementation allows you to spin up a language service and communicate with it via a named pipe transport. This article demonstrates **example usage of typescript-go with a simple Go API**, covering the complete workflow from server initialization to symbol resolution.

## Understanding the Three Operation Modes

The compiler supports three distinct entry points defined in [`cmd/tsgo/main.go`](https://github.com/microsoft/typescript-go/blob/main/cmd/tsgo/main.go):

- **CLI Mode** (default) – Compiles `.ts` files directly via `execute.CommandLine()`, mirroring the traditional `tsc` experience.
- **LSP Mode** (`--lsp`) – Starts a Language Server Protocol server for editor integration, implemented in [`internal/lsp/server.go`](https://github.com/microsoft/typescript-go/blob/main/internal/lsp/server.go).
- **API Mode** (`--api`) – Starts a pipe-based server that exposes the programmatic API defined in [`internal/api/session.go`](https://github.com/microsoft/typescript-go/blob/main/internal/api/session.go).

When you invoke `tsgo --api`, the `runAPI()` function creates a new `api.Session` and begins listening on a uniquely named pipe generated by `generatePipePath()`.

## Starting the API Server

To expose the TypeScript compiler as a service, run the binary with the `--api` flag:

```bash
tsgo --api

```

This command blocks and prints a pipe path (e.g., `\\.\pipe\tsgo-api-abcdef1234567890`) to stdout. Internally, [`cmd/tsgo/api.go`](https://github.com/microsoft/typescript-go/blob/main/cmd/tsgo/api.go) initializes the server using the `api.Session` type, which manages **project sessions** and **snapshots**—isolated type-checking contexts that preserve stable handles for symbols and types across wire requests.

## Connecting from a Go Client

Once the server is running, any Go process can connect to the named pipe and issue requests. The client workflow involves three main steps: establishing transport, creating a session, and executing API calls.

### Establishing the Pipe Transport

Use `api.NewPipeTransport()` to open the connection using the pipe path printed by the server:

```go
pipePath := `\\.\pipe\tsgo-api-abcdef1234567890`
transport, err := api.NewPipeTransport(pipePath)
if err != nil {
    log.Fatalf("cannot open pipe: %v", err)
}

```

This transport implements the connection layer that speaks the protocol defined in [`internal/api/protocol.go`](https://github.com/microsoft/typescript-go/blob/main/internal/api/protocol.go).

### Initializing the Session

Create an API session and wrap it in an async connection handler. The `api.NewSession()` function accepts project options (or `nil` for defaults) and returns a `*Session` that implements the `api.Handler` interface:

```go
session := api.NewSession(nil, nil) // nil uses default project session
client := conn.NewAsyncConn(transport, session)

go func() {
    if err := client.Run(context.Background()); err != nil {
        log.Fatalf("API connection error: %v", err)
    }
}()

```

The `conn.NewAsyncConn` constructor, defined in [`internal/api/conn.go`](https://github.com/microsoft/typescript-go/blob/main/internal/api/conn.go), runs the request loop in a background goroutine.

### Querying Symbols and Types

All API requests are method calls on the session. The typical workflow involves updating the snapshot, retrieving a symbol handle, and resolving its type:

1. **UpdateSnapshot** – Opens a project or syncs files via `handleUpdateSnapshot`, returning a snapshot handle.
2. **GetSymbolAtPosition** – Returns a stable handle to the symbol at a specific UTF-16 offset.
3. **GetTypeOfSymbol** – Resolves the type associated with the symbol handle.
4. **TypeToString** – Generates a human-readable string representation of the type.

Request and response structs like `UpdateSnapshotParams` and `SymbolResponse` are defined in [`internal/api/protocol.go`](https://github.com/microsoft/typescript-go/blob/main/internal/api/protocol.go).

## Complete Working Example

The following program demonstrates a minimal client that connects to a running `tsgo --api` server, opens a project, and prints the type of a symbol at a specific position:

```go
package main

import (
    "context"
    "log"
    "time"

    "github.com/microsoft/typescript-go/internal/api"
    "github.com/microsoft/typescript-go/internal/api/conn"
)

func main() {
    // Replace with the actual pipe printed by `tsgo --api`
    pipePath := `\\.\pipe\tsgo-api-abcdef1234567890`

    // Connect to the API server
    transport, err := api.NewPipeTransport(pipePath)
    if err != nil {
        log.Fatalf("cannot open pipe: %v", err)
    }

    // Create a session (no custom options needed for a simple demo)
    session := api.NewSession(nil, nil)

    // Async connection runs in its own goroutine
    client := conn.NewAsyncConn(transport, session)
    go func() {
        if err := client.Run(context.Background()); err != nil {
            log.Fatalf("API connection error: %v", err)
        }
    }()

    // Give the connection a moment to start
    time.Sleep(100 * time.Millisecond)

    // 1️⃣  Update snapshot – open a project (tsconfig.json) and sync files
    upd := &api.UpdateSnapshotParams{
        OpenProject: "example/tsconfig.json",
    }
    updResp, err := session.HandleRequest(context.Background(),
        string(api.MethodUpdateSnapshot), upd)
    if err != nil {
        log.Fatalf("UpdateSnapshot failed: %v", err)
    }
    snapshotHandle := updResp.(*api.UpdateSnapshotResponse).Snapshot

    // 2️⃣  Ask for the symbol at line 5, column 10 of a source file
    symReq := &api.GetSymbolAtPositionParams{
        Snapshot: snapshotHandle,
        Project:  updResp.(*api.UpdateSnapshotResponse).Projects[0].Handle,
        File:     api.NewFileReference("example/src/main.ts"),
        Position: 120, // UTF‑16 offset (adjust for your file)
    }
    symResp, err := session.HandleRequest(context.Background(),
        string(api.MethodGetSymbolAtPosition), symReq)
    if err != nil {
        log.Fatalf("GetSymbolAtPosition failed: %v", err)
    }
    symHandle := symResp.(*api.SymbolResponse).Id

    // 3️⃣  Resolve the symbol’s type
    typReq := &api.GetTypeOfSymbolParams{
        Snapshot: snapshotHandle,
        Project:  updResp.(*api.UpdateSnapshotResponse).Projects[0].Handle,
        Symbol:   symHandle,
    }
    typResp, err := session.HandleRequest(context.Background(),
        string(api.MethodGetTypeOfSymbol), typReq)
    if err != nil {
        log.Fatalf("GetTypeOfSymbol failed: %v", err)
    }

    // Print a human‑readable representation of the type
    tStrReq := &api.TypeToStringParams{
        Snapshot: snapshotHandle,
        Project:  updResp.(*api.UpdateSnapshotResponse).Projects[0].Handle,
        Type:     typResp.(*api.TypeResponse).Id,
    }
    str, err := session.HandleRequest(context.Background(),
        string(api.MethodTypeToString), tStrReq)
    if err != nil {
        log.Fatalf("TypeToString failed: %v", err)
    }
    log.Printf("Resolved type: %s", str.(string))
}

```

This example demonstrates how the `Session` type routes requests through `HandleRequest()`, using typed handles (`Handle[project.Snapshot]`, symbol IDs) that maintain identity across the pipe boundary.

## Key Source Files and Architecture

Understanding the following files helps when extending the API client:

- **[`cmd/tsgo/main.go`](https://github.com/microsoft/typescript-go/blob/main/cmd/tsgo/main.go)** – Entry point that dispatches to `runLSP()`, `runAPI()`, or standard compilation.
- **[`cmd/tsgo/api.go`](https://github.com/microsoft/typescript-go/blob/main/cmd/tsgo/api.go)** – Implements `runAPI()`, which instantiates the server session and pipe listener.
- **[`internal/api/session.go`](https://github.com/microsoft/typescript-go/blob/main/internal/api/session.go)** – Core implementation of `api.Handler`, including `handleUpdateSnapshot` and `handleGetSymbolAtPosition`.
- **[`internal/api/protocol.go`](https://github.com/microsoft/typescript-go/blob/main/internal/api/protocol.go)** – Defines all request/response structs (e.g., `GetSymbolAtPositionParams`, `TypeToStringParams`).
- **[`internal/api/conn.go`](https://github.com/microsoft/typescript-go/blob/main/internal/api/conn.go)** – Defines the `Handler` interface used by both LSP and API connections.

The architecture uses **snapshots** (instances of `project.Snapshot`) to isolate type-checking state. Each snapshot owns its own symbol and type registries, ensuring that handles returned by `snapshotHandle()` in [`internal/api/session.go`](https://github.com/microsoft/typescript-go/blob/main/internal/api/session.go) remain valid for subsequent queries.

## Summary

- **`tsgo --api`** starts a pipe-based server that exposes the TypeScript compiler's language service.
- **`api.NewPipeTransport()`** and **`api.NewSession()`** establish the client connection to the server.
- **Request structs** in [`internal/api/protocol.go`](https://github.com/microsoft/typescript-go/blob/main/internal/api/protocol.go) define the protocol for updating snapshots, querying symbols, and resolving types.
- **Handles** provide stable references to snapshots, symbols, and types across the wire, managed by registries in the API session.
- The LSP server in [`internal/lsp/server.go`](https://github.com/microsoft/typescript-go/blob/main/internal/lsp/server.go) shares the same snapshot infrastructure, ensuring consistency between programmatic API usage and editor integrations.

## Frequently Asked Questions

### What is the difference between typescript-go and the standard TypeScript compiler?

**typescript-go is a complete rewrite of the TypeScript compiler in Go**, providing equivalent type-checking and emit capabilities to the Node.js-based `tsc` but with significantly faster performance. While `tsc` runs as a Node.js process, `typescript-go` compiles to a native binary and exposes additional entry points for programmatic use via the API mode, which is not available in the standard distribution.

### How does the pipe transport work in typescript-go?

The pipe transport uses **named pipes** (Windows) or Unix domain sockets to communicate between the `tsgo --api` server and client processes. When the server starts, `generatePipePath()` in [`internal/api/session.go`](https://github.com/microsoft/typescript-go/blob/main/internal/api/session.go) creates a unique path like `\\.\pipe\tsgo-api-<timestamp>-<random>`. The client connects via `api.NewPipeTransport()`, and both sides exchange MessagePack-encoded requests defined in [`internal/api/protocol.go`](https://github.com/microsoft/typescript-go/blob/main/internal/api/protocol.go).

### Can I use typescript-go as a drop-in replacement for tsc?

**Yes**, for standard compilation workflows.** Running `tsgo` without flags invokes `execute.CommandLine()` in [`internal/execute/execute.go`](https://github.com/microsoft/typescript-go/blob/main/internal/execute/execute.go), which accepts the same command-line arguments as `tsc` and produces identical JavaScript output. However, some legacy configuration options and plugin APIs may differ given the port is currently in preview status.

### What are snapshots and handles in the API?

**Snapshots** (`project.Snapshot`) are immutable type-checking contexts that cache parsed source files and their associated symbol tables. **Handles** (e.g., `Handle[project.Snapshot]`) are string-encoded references generated by `snapshotHandle()` that allow the API to refer to complex objects over the wire without serializing the entire AST. When you call `UpdateSnapshot`, the server creates a new snapshot and returns its handle, which you then supply to subsequent requests like `GetSymbolAtPosition`.