Bun TypeScript Support

Bun has native TypeScript support, allowing you to run .ts and .tsx files directly without any configuration. This chapter introduces Bun's TypeScript features and best practices.

Out of the Box

Running TypeScript Directly

// app.ts
interface User {
  id: number;
  name: string;
  email: string;
}

function greet(user: User): string {
  return `Hello, ${user.name}!`;
}

const user: User = {
  id: 1,
  name: "Zhang San",
  email: "zhangsan@example.com"
};

console.log(greet(user));

Run:

bun app.ts
# Output: Hello, Zhang San!

No need to:

  • Install typescript
  • Configure ts-node
  • Pre-compilation step

TypeScript Configuration

Auto-generate tsconfig.json

# Auto-create during project initialization
bun init

# Or create manually
bun tsc --init
{
  "compilerOptions": {
    // Target and module
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    
    // Strict mode
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    
    // Path configuration
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    
    // JSX support
    "jsx": "react-jsx",
    
    // Other options
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    
    // Output directory
    "outDir": "./dist",
    "rootDir": "./src",
    
    // Bun types
    "types": ["bun-types"]
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Bun Type Definitions

# Install Bun type definitions
bun add -d @types/bun

Get complete Bun API type hints:

// Now you have complete type support
const file = Bun.file("./data.txt");
const content: string = await file.text();

const server = Bun.serve({
  port: 3000,
  fetch(request: Request): Response {
    return new Response("Hello!");
  }
});

Type Checking

Running Type Checks

Bun runtime does not perform type checking (for speed), you need to run it separately:

# Use TypeScript compiler to check
bun tsc --noEmit

# Or configure in package.json
{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "dev": "bun --watch src/index.ts",
    "build": "bun run typecheck && bun build src/index.ts --outdir dist"
  }
}

Type Error Example

// error.ts
function add(a: number, b: number): number {
  return a + b;
}

// TypeScript will report an error, but Bun will still run
add("1", 2);  // Type error
# Bun will run (ignoring type errors)
bun error.ts

# TypeScript check will report errors
bun tsc --noEmit
# error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

TSX / JSX Support

React Components

// App.tsx
import React from "react";

interface Props {
  name: string;
  age?: number;
}

const Greeting: React.FC<Props> = ({ name, age }) => {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      {age && <p>Age: {age}</p>}
    </div>
  );
};

export default Greeting;

Server-Side Rendering

// server.tsx
import { renderToString } from "react-dom/server";
import App from "./App";

const server = Bun.serve({
  port: 3000,
  fetch(request) {
    const html = renderToString(<App name="Bun" />);
    return new Response(`
      <!DOCTYPE html>
      <html>
        <head><title>Bun React SSR</title></head>
        <body>
          <div id="root">${html}</div>
        </body>
      </html>
    `, {
      headers: { "Content-Type": "text/html" }
    });
  }
});

JSX Configuration

// tsconfig.json
{
  "compilerOptions": {
    // React 17+ new JSX transform
    "jsx": "react-jsx",
    "jsxImportSource": "react",
    
    // Or use classic transform
    // "jsx": "react",
    
    // Use Preact
    // "jsx": "react-jsx",
    // "jsxImportSource": "preact"
  }
}

Type Imports

Importing Types

// types.ts
export interface User {
  id: number;
  name: string;
}

export type Status = "active" | "inactive" | "pending";

// main.ts
// Type import (not included in runtime code)
import type { User, Status } from "./types";

// Or mixed import
import { type User, type Status } from "./types";

function processUser(user: User, status: Status) {
  console.log(`${user.name}: ${status}`);
}

Inline Type Imports

// Ensure types are only used in type context
import { type User } from "./types";

Generics

Generic Functions

function identity<T>(value: T): T {
  return value;
}

const num = identity<number>(42);
const str = identity("hello");  // Type inference

// Generic constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "Zhang San", age: 25 };
const name = getProperty(user, "name");  // string

Generic Classes

class Container<T> {
  private value: T;
  
  constructor(value: T) {
    this.value = value;
  }
  
  getValue(): T {
    return this.value;
  }
  
  setValue(value: T): void {
    this.value = value;
  }
}

const numberContainer = new Container<number>(42);
const stringContainer = new Container("hello");

Generic Interfaces

interface Repository<T> {
  findById(id: number): Promise<T | null>;
  findAll(): Promise<T[]>;
  save(entity: T): Promise<T>;
  delete(id: number): Promise<boolean>;
}

interface User {
  id: number;
  name: string;
}

class UserRepository implements Repository<User> {
  private users: User[] = [];
  
  async findById(id: number): Promise<User | null> {
    return this.users.find(u => u.id === id) || null;
  }
  
  async findAll(): Promise<User[]> {
    return this.users;
  }
  
  async save(user: User): Promise<User> {
    this.users.push(user);
    return user;
  }
  
  async delete(id: number): Promise<boolean> {
    const index = this.users.findIndex(u => u.id === id);
    if (index !== -1) {
      this.users.splice(index, 1);
      return true;
    }
    return false;
  }
}

Decorators

Enabling Decorators

// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Class Decorators

function Logger(constructor: Function) {
  console.log(`Class ${constructor.name} was created`);
}

@Logger
class MyService {
  constructor() {
    console.log("MyService instantiated");
  }
}

// Output:
// Class MyService was created
// MyService instantiated

Method Decorators

function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${propertyKey}, args:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`${propertyKey} returned:`, result);
    return result;
  };
  
  return descriptor;
}

class Calculator {
  @Log
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(2, 3);
// Output:
// Calling add, args: [2, 3]
// add returned: 5

Utility Types

Common Utility Types

interface User {
  id: number;
  name: string;
  email: string;
  age?: number;
}

// Partial - all properties optional
type PartialUser = Partial<User>;

// Required - all properties required
type RequiredUser = Required<User>;

// Readonly - all properties readonly
type ReadonlyUser = Readonly<User>;

// Pick - select some properties
type UserBasic = Pick<User, "id" | "name">;

// Omit - exclude some properties
type UserWithoutEmail = Omit<User, "email">;

// Record - create object type
type UserMap = Record<number, User>;

// Extract / Exclude - type filtering
type Status = "active" | "inactive" | "pending";
type ActiveStatus = Extract<Status, "active" | "pending">;
type InactiveStatus = Exclude<Status, "active">;

Custom Utility Types

// Deep readonly
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};

// Non-nullable fields
type NonNullableFields<T> = {
  [K in keyof T]: NonNullable<T[K]>;
};

// Function parameter types
type FunctionParams<T> = T extends (...args: infer P) => any ? P : never;

Type Guards

Type Guard Functions

interface Dog {
  kind: "dog";
  bark(): void;
}

interface Cat {
  kind: "cat";
  meow(): void;
}

type Animal = Dog | Cat;

// Type guard
function isDog(animal: Animal): animal is Dog {
  return animal.kind === "dog";
}

function handleAnimal(animal: Animal) {
  if (isDog(animal)) {
    animal.bark();  // TypeScript knows this is Dog
  } else {
    animal.meow();  // TypeScript knows this is Cat
  }
}

in Operator

interface Admin {
  role: "admin";
  permissions: string[];
}

interface User {
  role: "user";
  profile: object;
}

function handlePerson(person: Admin | User) {
  if ("permissions" in person) {
    console.log(person.permissions);  // Admin
  } else {
    console.log(person.profile);  // User
  }
}

Building TypeScript Projects

Compilation Output

# Use Bun to bundle (transpile to JavaScript)
bun build src/index.ts --outdir dist

# Generate type definitions
bun tsc --emitDeclarationOnly

Complete Build Process

{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "build": "bun run typecheck && bun build src/index.ts --outdir dist --target node",
    "build:types": "tsc --emitDeclarationOnly --outDir dist",
    "prepublish": "bun run build && bun run build:types"
  }
}

Summary

This chapter introduced:

  • ✅ Bun's native TypeScript support
  • ✅ tsconfig.json configuration
  • ✅ TSX/JSX support
  • ✅ Type checking and type imports
  • ✅ Generics and decorators
  • ✅ Utility types and type guards
  • ✅ Building TypeScript projects

Next Steps

Continue reading Environment Variables to learn about Bun's environment configuration management.