Lesson 03-Bun.js Application Basics

Web Development Basics

Creating an HTTP Server

Basic HTTP Server Implementation:

import { serve } from 'bun'

const server = serve({
  port: 3000,
  fetch(request) {
    // Handle request and return response
    return new Response('Hello Bun!', {
      headers: { 'Content-Type': 'text/plain' }
    })
  }
})

console.log(`Server running at http://localhost:${server.port}`)

Advanced Server Configuration:

serve({
  port: 3000,
  hostname: '0.0.0.0',  // Listen on all network interfaces
  development: true,    // Development mode (auto-reload)
  https: {              // HTTPS configuration
    certFile: './cert.pem',
    keyFile: './key.pem'
  },
  fetch(request, server) {
    // Access server object for additional information
    const url = new URL(request.url)
    return handleRequest(url.pathname, request)
  }
})

Routing and Request Handling

Custom Routing Implementation:

const routes = {
  '/': () => new Response('Home Page'),
  '/about': () => new Response('About Page'),
  '/api/users': async () => {
    const users = await getUsersFromDB()
    return new Response(JSON.stringify(users), {
      headers: { 'Content-Type': 'application/json' }
    })
  }
}

serve({
  fetch(request) {
    const url = new URL(request.url)
    const handler = routes[url.pathname]
    if (handler) {
      return handler(request)
    }
    return new Response('404 Not Found', { status: 404 })
  }
})

Middleware Mechanism Implementation:

function createMiddleware(handler, ...middlewares) {
  return async (request) => {
    let chain = handler
    // Wrap middleware in reverse order
    for (const middleware of middlewares.reverse()) {
      const next = chain
      chain = () => middleware(request, next)
    }
    return chain()
  }
}

// Usage example
const logger = (request, next) => {
  console.log(`${request.method} ${request.url}`)
  return next()
}

const auth = (request, next) => {
  const token = request.headers.get('Authorization')
  if (!token) throw new Error('Unauthorized')
  return next()
}

const handler = createMiddleware(
  (request) => new Response('Protected Content'),
  logger,
  auth
)

Static File Serving

Static File Configuration:

serve({
  port: 3000,
  fetch(request) {
    const url = new URL(request.url)
    if (url.pathname.startsWith('/static/')) {
      // Serve static files directly from the file system
      return Bun.file(`./public${url.pathname}`).response
    }
    // Other requests handled by dynamic routing
    return handleDynamicRequest(request)
  }
})

Optimized Static File Serving:

import { MIME_TYPES } from './mime-types'

serve({
  fetch(request) {
    const url = new URL(request.url)
    const filePath = `./public${url.pathname}`

    // Check if file exists
    if (Bun.file(filePath).exists()) {
      // Get file extension to set Content-Type
      const ext = filePath.split('.').pop()
      const contentType = MIME_TYPES[ext] || 'application/octet-stream'

      // Set cache headers
      const headers = new Headers({
        'Content-Type': contentType,
        'Cache-Control': 'public, max-age=3600'
      })

      return Bun.file(filePath).response
        .then(response => new Response(response.body, {
          headers,
          status: response.status
        }))
    }
    return new Response('Not Found', { status: 404 })
  }
})

Template Engine Integration

EJS Template Integration:

import { serve } from 'bun'
import ejs from 'ejs'

serve({
  fetch(request) {
    if (request.url === '/home') {
      const data = { title: 'Home Page', user: { name: 'Bun User' } }
      const html = ejs.renderFile('./views/home.ejs', data)
      return new Response(html, {
        headers: { 'Content-Type': 'text/html' }
      })
    }
    return new Response('Not Found', { status: 404 })
  }
})

Pug Template Integration:

import { serve } from 'bun'
import pug from 'pug'

serve({
  fetch(request) {
    if (request.url === '/about') {
      const data = { title: 'About Us', features: ['Fast', 'Lightweight'] }
      const html = pug.renderFile('./views/about.pug', data)
      return new Response(html, {
        headers: { 'Content-Type': 'text/html' }
      })
    }
    return new Response('Not Found', { status: 404 })
  }
})

RESTful API Design and Implementation

RESTful API Example:

import { serve } from 'bun'

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
]

serve({
  fetch(request) {
    const url = new URL(request.url)
    const id = parseInt(url.searchParams.get('id'))

    switch (url.pathname) {
      case '/api/users':
        if (request.method === 'GET') {
          return new Response(JSON.stringify(users), {
            headers: { 'Content-Type': 'application/json' }
          })
        } else if (request.method === 'POST') {
          const user = await request.json()
          users.push(user)
          return new Response(JSON.stringify(user), {
            status: 201,
            headers: { 'Content-Type': 'application/json' }
          })
        }
        break

      case `/api/users/${id}`:
        if (request.method === 'GET') {
          const user = users.find(u => u.id === id)
          if (user) {
            return new Response(JSON.stringify(user), {
              headers: { 'Content-Type': 'application/json' }
            })
          }
          return new Response('User not found', { status: 404 })
        } else if (request.method === 'DELETE') {
          users = users.filter(u => u.id !== id)
          return new Response(null, { status: 204 })
        }
        break
    }

    return new Response('Not Found', { status: 404 })
  }
})

API Best Practices:

  1. Use appropriate HTTP status codes
  2. Standardize response formats
  3. Implement request validation
  4. Add version control (/api/v1/users)
  5. Use middleware for CORS and authentication

Database Interaction

MySQL Database Connection

mysql2 Basic Operations:

import mysql from 'mysql2/promise'

async function main() {
  // Create connection pool
  const pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'test_db',
    waitForConnections: true,
    connectionLimit: 10,
    queueLimit: 0
  })

  // Execute query
  const [rows] = await pool.execute('SELECT * FROM users WHERE id = ?', [1])
  console.log(rows)

  // Insert data
  const [result] = await pool.execute(
    'INSERT INTO users (name, email) VALUES (?, ?)',
    ['Bun User', 'user@example.com']
  )
  console.log('Inserted ID:', result.insertId)

  await pool.end()
}

main()

PostgreSQL Database Operations

pg Module Usage:

import { Client } from 'pg'

async function main() {
  const client = new Client({
    user: 'postgres',
    host: 'localhost',
    database: 'test_db',
    password: 'password',
    port: 5432,
  })

  await client.connect()

  // Execute query
  const res = await client.query('SELECT * FROM users WHERE id = $1', [1])
  console.log(res.rows)

  // Transaction handling
  await client.query('BEGIN')
  try {
    await client.query('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [100, 1])
    await client.query('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [100, 2])
    await client.query('COMMIT')
  } catch (err) {
    await client.query('ROLLBACK')
    throw err
  }

  await client.end()
}

main()

SQLite Database Operations

better-sqlite3 Usage:

import Database from 'better-sqlite3'

const db = new Database('test.db')

// Create table
db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL
  )
`)

// Insert data
const stmt = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
const info = stmt.run('Bun User', 'user@example.com')
console.log('Inserted ID:', info.lastInsertRowid)

// Query data
const rows = db.prepare('SELECT * FROM users').all()
console.log(rows)

db.close()

Database Connection Pool Optimization

Connection Pool Best Practices:

// Optimized MySQL connection pool configuration
const pool = mysql.createPool({
  connectionLimit: 10,          // Maximum connections
  queueLimit: 0,                // Unlimited queuing
  acquireTimeout: 10000,        // Connection acquisition timeout (ms)
  idleTimeout: 60000,           // Idle connection timeout
  enableKeepAlive: true,        // Keep connections alive
  keepAliveInitialDelay: 0      // Initial delay
})

// PostgreSQL connection pool
import { Pool } from 'pg'
const pool = new Pool({
  max: 20,                      // Maximum clients
  idleTimeoutMillis: 30000,     // Idle connection timeout
  connectionTimeoutMillis: 2000 // Connection timeout
})

Performance Optimization Techniques:

  1. Use prepared statements
  2. Batch operations instead of single operations
  3. Set appropriate connection pool size
  4. Implement connection health checks
  5. Use caching to reduce database queries

ORM and ODM Selection

Prisma Example:

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  // Create record
  const user = await prisma.user.create({
    data: {
      name: 'Bun User',
      email: 'user@example.com'
    }
  })

  // Query records
  const users = await prisma.user.findMany({
    where: {
      email: { contains: 'example' }
    }
  })

  // Complex query
  const result = await prisma.$queryRaw`
    SELECT * FROM users 
    WHERE id IN (SELECT user_id FROM posts WHERE views > 100)
  `
}

main()

TypeORM Example:

import "reflect-metadata"
import { createConnection } from "typeorm"
import { User } from "./entity/User"

createConnection().then(async connection => {
  const user = new User()
  user.firstName = "Bun"
  user.lastName = "User"
  await connection.manager.save(user)

  const users = await connection.manager.find(User)
  console.log(users)
}).catch(error => console.log(error))

Selection Recommendations:

RequirementRecommended Tool
Rapid DevelopmentPrisma
Complex QueriesTypeORM
MongoDBMongoose (ODM)
High PerformanceDirect SQL libraries

Command-Line Tool Development

Command-Line Argument Parsing

minimist Usage Example:

import minimist from 'minimist'

const argv = minimist(process.argv.slice(2))
console.log(argv)

// Usage example:
// node cli.js --name=Bun --verbose --count=3
// Output: { _: [], name: 'Bun', verbose: true, count: 3 }

yargs Advanced Usage:

import yargs from 'yargs/yargs'
import { hideBin } from 'yargs/helpers'

yargs(hideBin(process.argv))
  .option('name', {
    alias: 'n',
    type: 'string',
    description: 'Your name'
  })
  .option('verbose', {
    alias: 'v',
    type: 'boolean',
    description: 'Verbose output'
  })
  .command('greet', 'Greet the user', () => {}, (argv) => {
    console.log(`Hello, ${argv.name}!`)
  })
  .help()
  .argv

File System Operations

File Operation Example:

import { 
  readFileSync, 
  writeFileSync, 
  readdirSync,
  statSync
} from 'fs'
import path from 'path'

// Read file
const content = readFileSync('config.json', 'utf-8')
console.log(content)

// Write file
writeFileSync('output.txt', 'Hello Bun!')

// Traverse directory
const files = readdirSync('./src')
files.forEach(file => {
  const filePath = path.join('./src', file)
  const stats = statSync(filePath)
  if (stats.isDirectory()) {
    console.log('Directory:', file)
  } else {
    console.log('File:', file)
  }
})

Colored Output and Progress Bars

chalk Usage Example:

import chalk from 'chalk'

console.log(chalk.blue('Hello world!'))
console.log(chalk.red.bold('Error message'))
console.log(chalk.green('Success message'))
console.log(chalk.yellow.inverse('Warning'))

// Combine styles
console.log(chalk.blue.bgRed.bold('Red on blue'))

ora Progress Bar Example:

import ora from 'ora'

const spinner = ora('Loading unicorns').start()

setTimeout(() => {
  spinner.color = 'yellow'
  spinner.text = 'Loading rainbows'
}, 1000)

setTimeout(() => {
  spinner.succeed('Done!')
}, 2000)

Scaffolding Tool Development

Basic Scaffolding Implementation:

#!/usr/bin/env bun

import { copySync, ensureDirSync, existsSync } from 'fs-extra'
import path from 'path'
import chalk from 'chalk'

// Project template directory
const templateDir = path.join(__dirname, 'templates', 'basic')

// Create project
async function createProject(projectName) {
  const projectPath = path.join(process.cwd(), projectName)

  if (existsSync(projectPath)) {
    console.error(chalk.red(`Error: ${projectName} already exists`))
    process.exit(1)
  }

  console.log(chalk.blue(`Creating project ${projectName}...`))

  // Copy template files
  ensureDirSync(projectPath)
  copySync(templateDir, projectPath)

  // Replace placeholders
  const packageJsonPath = path.join(projectPath, 'package.json')
  let packageJson = readFileSync(packageJsonPath, 'utf-8')
  packageJson = packageJson.replace(/{{projectName}}/g, projectName)
  writeFileSync(packageJsonPath, packageJson)

  console.log(chalk.green(`Success: ${projectName} created successfully`))
}

// Parse command-line arguments
const argv = process.argv.slice(2)
if (argv.length === 0) {
  console.log(chalk.yellow('Usage: bun run cli.js <project-name>'))
  process.exit(1)
}

createProject(argv[0])

Global vs Local Installation

Installation Methods Comparison:

FeatureGlobal InstallationLocal Installation
Installation LocationSystem-wide directoryProject node_modules
UsageDirect command-line invocationVia npx or package.json scripts
Use CaseCommand-line toolsProject dependency tools
Version ManagementSingle versionDifferent versions per project

package.json Script Configuration:

{
  "scripts": {
    "lint": "eslint src",
    "format": "prettier --write src",
    "build": "bun build src/index.ts --outfile dist/bundle.js"
  }
}

Global Tool Development Recommendations:

  1. Provide clear error messages
  2. Support –help parameter for help information
  3. Implement version checking (–version)
  4. Use colored output for improved readability
  5. Add progress indicators (ora)
Share your love