Skip to content

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

typescript
// 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:

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

No need to:

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

TypeScript Configuration

Auto-generate tsconfig.json

bash
# Auto-create during project initialization
bun init

# Or create manually
bun tsc --init
json
{
  "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

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

Get complete Bun API type hints:

typescript
// 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:

bash
# 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

typescript
// 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
bash
# 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

tsx
// 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

tsx
// 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

json
// 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

typescript
// 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

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

Generics

Generic Functions

typescript
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

typescript
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

typescript
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

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

Class Decorators

typescript
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

typescript
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

typescript
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

typescript
// 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

typescript
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

typescript
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

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

# Generate type definitions
bun tsc --emitDeclarationOnly

Complete Build Process

json
{
  "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.

Content is for learning and research only.