Server-Side Rendering (SSR)
SSR Core Implementation
Bun.js SSR Core Architecture:
// src/server/render.ts
import { serve } from 'bun'
import React from 'react'
import { renderToString } from 'react-dom/server'
import App from '../app' // React application entry point
const server = serve({
port: 3000,
fetch(request) {
// 1. Parse request URL to get route information
const url = new URL(request.url)
// 2. Data prefetching (simulated API call)
const prefetchData = async () => {
// Replace with actual API call in real projects
return { user: { name: 'Bun User', id: 1 } }
}
// 3. Server-side rendering
const html = renderToString(
<App initialData={prefetchData()} url={url.pathname} />
)
// 4. Inject initial state into HTML
const initialState = JSON.stringify(prefetchData())
const finalHtml = `
<!DOCTYPE html>
<html>
<head>
<title>Bun SSR App</title>
</head>
<body>
<div id="root">${html}</div>
<script>window.__INITIAL_STATE__ = ${initialState}</script>
<script src="/client.js"></script>
</body>
</html>
`
return new Response(finalHtml, {
headers: { 'Content-Type': 'text/html' }
})
}
})
console.log('SSR Server running on http://localhost:3000')Template Engine Integration (Edge Template):
// src/server/edgeRender.ts
import { serve } from 'bun'
import { Edge } from 'edge.js' // Assuming Edge template engine is used
const edge = new Edge({
views: `${process.cwd()}/src/views` // Template directory
})
const server = serve({
port: 3000,
fetch(request) {
const url = new URL(request.url)
// Render using Edge template
const html = edge.render('home', {
title: 'Bun SSR with Edge',
user: { name: 'Edge User' }
})
return new Response(html, {
headers: { 'Content-Type': 'text/html' }
})
}
})
console.log('Edge SSR Server running on http://localhost:3000')Data Prefetching and State Synchronization
Data Prefetching Strategy Implementation:
// src/utils/dataFetcher.ts
export async function prefetchRouteData(url: string) {
// Simulated API requests
const mockApi = {
'/': () => Promise.resolve({ featuredPosts: [1, 2, 3] }),
'/about': () => Promise.resolve({ team: ['Alice', 'Bob'] }),
'/posts/:id': async (params: { id: string }) => {
// Simulated post details fetch
return {
post: {
id: params.id,
title: `Post ${params.id}`,
content: 'Sample content'
}
}
}
}
// Route matching and data prefetching
if (url === '/') return mockApi['/']()
if (url === '/about') return mockApi['/about']()
// Dynamic route handling
const dynamicMatch = url.match(/^\/posts\/(\d+)$/)
if (dynamicMatch) {
return mockApi['/posts/:id']({ id: dynamicMatch[1] })
}
return {} // Default to empty data
}
// Usage in SSR flow
const data = await prefetchRouteData(url.pathname)
const html = renderToString(<App initialData={data} />)Client-Side State Synchronization:
// src/app.tsx (React client entry point)
import React from 'react'
import { hydrateRoot } from 'react-dom/client'
interface AppProps {
initialData: any
url: string
}
export default function App({ initialData, url }: AppProps) {
// Use initial state to avoid duplicate requests
const [data, setData] = React.useState(initialData)
React.useEffect(() => {
// Fetch new data during client navigation
if (!initialData) {
prefetchRouteData(url).then(setData)
}
}, [url])
return (
<div>
<h1>Bun SSR App</h1>
{/* Render data... */}
</div>
)
}
// Client-side hydration
if (typeof document !== 'undefined') {
hydrateRoot(
document.getElementById('root')!,
<App initialData={window.__INITIAL_STATE__} url={window.location.pathname} />
)
}SSR Performance Optimization
Component-Level Caching Strategy:
// src/cache/componentCache.ts
const componentCache = new Map<string, string>()
export function getCachedComponent(key: string, renderFn: () => string) {
if (componentCache.has(key)) {
return componentCache.get(key)!
}
const html = renderFn()
componentCache.set(key, html)
// Set cache expiration (e.g., 10 minutes)
setTimeout(() => {
componentCache.delete(key)
}, 10 * 60 * 1000)
return html
}
// Usage example
const userHtml = getCachedComponent(`user-${userId}`, () => {
return renderToString(<UserProfile userId={userId} />)
})Streaming SSR Implementation:
// src/server/streamingRender.ts
import { serve } from 'bun'
import { renderToPipeableStream } from 'react-dom/server'
const server = serve({
port: 3000,
fetch(request) {
const url = new URL(request.url)
// Create writable stream
const stream = new WritableStream({
write(chunk) {
// Send HTML chunks
response.write(chunk)
},
close() {
response.end()
}
})
const response = new Response(stream, {
headers: { 'Content-Type': 'text/html' }
})
// Stream render React components
renderToPipeableStream(
<App url={url.pathname} />,
{
onShellReady() {
// Start sending when initial shell is ready
response.write('<!DOCTYPE html><html><head><title>Bun SSR</title></head><body><div id="root">')
},
onShellError(error) {
console.error('Shell rendering error:', error)
response.status = 500
response.write('Error rendering page')
response.end()
},
onCompleteShell() {
response.write('</div></body></html>')
},
onAllReady() {
// All content ready (optional)
}
},
stream
)
return response
}
})SSR Security and Error Handling
Security Measures:
// src/middleware/security.ts
import { Context, Next } from 'bun'
export function securityMiddleware(ctx: Context, next: Next) {
// 1. XSS Protection
ctx.response.headers.set('X-XSS-Protection', '1; mode=block')
// 2. CSP Policy
ctx.response.headers.set('Content-Security-Policy', [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' cdn.example.com",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data:"
].join('; '))
// 3. Clickjacking Protection
ctx.response.headers.set('X-Frame-Options', 'DENY')
// 4. MIME Type Sniffing Protection
ctx.response.headers.set('X-Content-Type-Options', 'nosniff')
return next()
}
// Usage in SSR flow
server.use(securityMiddleware)Error Boundary Handling:
// src/components/ErrorBoundary.tsx
import React from 'react'
interface State {
hasError: boolean
error?: Error
}
export class ErrorBoundary extends React.Component<{}, State> {
state: State = { hasError: false }
static getDerivedStateFromError(error: Error) {
return { hasError: true, error }
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('SSR Error:', error, info.componentStack)
// Optionally report error to monitoring system
}
render() {
if (this.state.hasError) {
// Return fallback UI
return (
<div>
<h1>Something went wrong</h1>
<p>Please refresh the page or try again later</p>
</div>
)
}
return this.props.children
}
}
// Usage in App
export default function App() {
return (
<ErrorBoundary>
{/* Application content */}
</ErrorBoundary>
)
}SSR Use Cases
SEO Optimization Implementation:
// src/server/seoRender.ts
import { serve } from 'bun'
const server = serve({
port: 3000,
fetch(request) {
const url = new URL(request.url)
// Dynamically generate SEO metadata
const seoData = {
'/': {
title: 'Home Page - Bun SSR Demo',
description: 'Welcome to our Bun.js powered website',
keywords: 'Bun,SSR,JavaScript'
},
'/about': {
title: 'About Us - Bun SSR Demo',
description: 'Learn more about our company',
keywords: 'About,Bun,Company'
}
}
const meta = seoData[url.pathname] || {
title: 'Default Title',
description: 'Default Description',
keywords: 'Bun,SSR'
}
const html = `
<!DOCTYPE html>
<html>
<head>
<title>${meta.title}</title>
<meta name="description" content="${meta.description}">
<meta name="keywords" content="${meta.keywords}">
</head>
<body>
<div id="root"></div>
<script src="/client.js"></script>
</body>
</html>
`
return new Response(html, {
headers: { 'Content-Type': 'text/html' }
})
}
})First Paint Performance Optimization:
// src/utils/performance.ts
export function optimizeFirstPaint() {
// 1. Inline critical CSS
const criticalCSS = `
body { font-family: sans-serif; margin: 0; }
.header { background: #fff; padding: 1rem; }
`
// 2. Defer non-critical JS
const lazyJs = `
<script defer src="/non-critical.js"></script>
`
// 3. Lazy-load images
const lazyImages = `
<img loading="lazy" src="/image.jpg" alt="Example">
`
return {
criticalCSS,
lazyJs,
lazyImages
}
}
// Integrate in SSR flow
const { criticalCSS, lazyJs, lazyImages } = optimizeFirstPaint()
const html = `
<!DOCTYPE html>
<html>
<head>
<style>${criticalCSS}</style>
</head>
<body>
<div id="root"></div>
${lazyImages}
${lazyJs}
</body>
</html>
`Static Site Generation (SSG)
SSG Core Implementation
Bun Build-Time Static File Generation:
// src/server/buildStatic.ts
import { build } from 'bun'
import fs from 'fs/promises'
import path from 'path'
import { renderToString } from 'react-dom/server'
import App from '../app'
async function generateStaticSite() {
// 1. Define route list
const routes = [
'/',
'/about',
'/blog/1',
'/blog/2'
]
// 2. Generate HTML files for each route
await Promise.all(routes.map(async (route) => {
// Simulated data fetch
const data = await getRouteData(route)
// Render React component to HTML string
const html = renderToString(<App initialData={data} />)
// Inject initial state
const finalHtml = `
<!DOCTYPE html>
<html>
<head>
<title>Bun SSG App</title>
</head>
<body>
<div id="root">${html}</div>
<script>window.__INITIAL_STATE__ = ${JSON.stringify(data)}</script>
<script src="/client.js"></script>
</body>
</html>
`
// Write to filesystem
const filePath = path.join(process.cwd(), 'dist', route === '/' ? 'index.html' : `${route}.html`)
await fs.mkdir(path.dirname(filePath), { recursive: true })
await fs.writeFile(filePath, finalHtml)
}))
console.log('Static site generated in dist directory')
}
// Execute build
build({
entrypoints: ['src/server/buildStatic.ts'],
outdir: 'dist-functions' // Optional: for Serverless deployment
})
// Run SSG generation
generateStaticSite()Dynamic Route Handling:
// src/utils/dynamicRoutes.ts
type RouteParams = {
[key: string]: string
}
export function matchDynamicRoute(
routePattern: string,
urlPath: string
): { matched: boolean; params?: RouteParams } {
// Convert route pattern to regex
const paramNames: string[] = []
const regexPattern = routePattern
.replace(/:(\w+)/g, (_, paramName) => {
paramNames.push(paramName)
return '([^/]+)'
})
.replace(/\//g, '\\/')
const regex = new RegExp(`^${regexPattern}$`)
const match = urlPath.match(regex)
if (!match) {
return { matched: false }
}
// Extract parameters
const params: RouteParams = {}
paramNames.forEach((name, index) => {
params[name] = match[index + 1]
})
return { matched: true, params }
}
// Usage example
const result = matchDynamicRoute('/blog/:id', '/blog/123')
if (result.matched) {
console.log('Params:', result.params) // { id: '123' }
}Content Management and Dynamic Routing
Markdown Content Processing:
// src/content/loadMarkdown.ts
import fs from 'fs/promises'
import path from 'path'
import marked from 'marked' // Markdown parsing library
export async function loadMarkdownFile(filePath: string) {
try {
const content = await fs.readFile(filePath, 'utf8')
const html = marked.parse(content)
// Extract front matter (e.g., title, date)
const frontMatterMatch = content.match(/---\n([\s\S]+?)\n---/)
const frontMatter = frontMatterMatch
? parseFrontMatter(frontMatterMatch[1])
: {}
return {
html,
frontMatter,
filePath
}
} catch (err) {
console.error('Failed to load markdown file:', err)
throw err
}
}
function parseFrontMatter(raw: string): Record<string, any> {
return raw.split('\n')
.filter(line => line.includes(':'))
.reduce((acc, line) => {
const [key, value] = line.split(':').map(s => s.trim())
acc[key] = isNaN(Number(value)) ? value : Number(value)
return acc
}, {} as Record<string, any>)
}
// Generate dynamic routes
export async function generateBlogRoutes() {
const blogDir = path.join(process.cwd(), 'content/blog')
const files = await fs.readdir(blogDir)
return Promise.all(
files.map(async (file) => {
const filePath = path.join(blogDir, file)
const { html, frontMatter } = await loadMarkdownFile(filePath)
return {
route: `/blog/${frontMatter.slug || file.replace('.md', '')}`,
content: html,
frontMatter
}
})
)
}SSG Performance Optimization
Incremental Static Regeneration (ISR) Simulation:
// src/server/isr.ts
import fs from 'fs/promises'
import path from 'path'
// Simulated cache storage
const cache = new Map<string, { html: string; timestamp: number; ttl: number }>()
export async function getStaticPageWithISR(route: string) {
const now = Date.now()
const cacheKey = `page-${route}`
// Check if cache exists and is valid
if (cache.has(cacheKey)) {
const entry = cache.get(cacheKey)!
if (now - entry.timestamp < entry.ttl) {
return entry.html // Return cached content
}
}
// Cache invalid or missing, regenerate
const html = await generateStaticPage(route)
// Update cache
cache.set(cacheKey, {
html,
timestamp: now,
ttl: 10 * 60 * 1000 // 10-minute TTL
})
return html
}
async function generateStaticPage(route: string) {
// Actual static page generation logic...
return `Generated content for ${route}`
}
// Periodically clean expired cache
setInterval(() => {
const now = Date.now()
for (const [key, entry] of cache.entries()) {
if (now - entry.timestamp > entry.ttl) {
cache.delete(key)
}
}
}, 60 * 1000) // Check every minuteStatic Asset Optimization:
// src/server/staticAssets.ts
import { serve } from 'bun'
import path from 'path'
const assetServer = serve({
port: 3001,
fetch(request) {
const url = new URL(request.url)
const filePath = path.join(process.cwd(), 'dist', url.pathname)
return Bun.file(filePath).response
}
})
// Enable asset optimization middleware in production
if (process.env.NODE_ENV === 'production') {
app.use(async (ctx, next) => {
// 1. Gzip compression
ctx.response.headers.set('content-encoding', 'gzip')
// 2. Cache control
if (ctx.request.url.endsWith('.js') || ctx.request.url.endsWith('.css')) {
ctx.response.headers.set('cache-control', 'public, max-age=31536000, immutable')
}
await next()
})
}SSG and CMS Integration
Headless CMS Data Fetching:
// src/content/cmsClient.ts
import axios from 'axios'
const CMS_API_URL = process.env.CMS_API_URL || 'https://cms.example.com/api'
export async function fetchCmsPage(slug: string) {
try {
const response = await axios.get(`${CMS_API_URL}/pages/${slug}`)
return response.data
} catch (err) {
console.error('Failed to fetch CMS page:', err)
throw err
}
}
// Example CMS response structure
interface CmsPage {
id: string
slug: string
title: string
content: string // HTML or Markdown
meta: {
description: string
keywords: string
}
}
// Generate static pages based on CMS content
export async function generateCmsPages() {
// 1. Fetch all page slugs
const pagesResponse = await axios.get(`${CMS_API_URL}/pages`)
const pages = pagesResponse.data
// 2. Generate static HTML for each page
await Promise.all(
pages.map(async (page: CmsPage) => {
const html = renderToString(
<App pageData={page} />
)
const filePath = path.join(process.cwd(), 'dist', `${page.slug}.html`)
await fs.mkdir(path.dirname(filePath), { recursive: true })
await fs.writeFile(filePath, `
<!DOCTYPE html>
<html>
<head>
<title>${page.title}</title>
<meta name="description" content="${page.meta.description}">
<meta name="keywords" content="${page.meta.keywords}">
</head>
<body>
<div id="root">${html}</div>
<script src="/client.js"></script>
</body>
</html>
`)
})
)
}SSG Use Cases
Documentation Site Generation:
// src/content/docsGenerator.ts
import fs from 'fs/promises'
import path from 'path'
import { renderToString } from 'react-dom/server'
import DocsLayout from '../components/DocsLayout'
import { loadMarkdownFile } from './loadMarkdown'
export async function generateDocsSite() {
const docsDir = path.join(process.cwd(), 'content/docs')
const files = await fs.readdir(docsDir)
// 1. Generate individual documentation pages
await Promise.all(
files.map(async (file) => {
const filePath = path.join(docsDir, file)
const { html: markdownHtml, frontMatter } = await loadMarkdownFile(filePath)
const docHtml = renderToString(
<DocsLayout content={markdownHtml} meta={frontMatter} />
)
const outputPath = path.join(
process.cwd(),
'dist',
'docs',
file.replace('.md', '.html')
)
await fs.mkdir(path.dirname(outputPath), { recursive: true })
await fs.writeFile(outputPath, `
<!DOCTYPE html>
<html>
<head>
<title>${frontMatter.title || 'Documentation'}</title>
</head>
<body>
<div id="root">${docHtml}</div>
<script src="/client.js"></script>
</body>
</html>
`)
})
)
// 2. Generate documentation index page
const docLinks = files.map(file => ({
title: path.basename(file, '.md'),
url: `/docs/${file.replace('.md', '')}`
}))
const indexHtml = renderToString(
<DocsLayout>
<ul>
{docLinks.map(link => (
<li key={link.url}>
<a href={link.url}>{link.title}</a>
</li>
))}
</ul>
</DocsLayout>
)
await fs.writeFile(
path.join(process.cwd(), 'dist', 'docs', 'index.html'),
`
<!DOCTYPE html>
<html>
<head>
<title>Documentation Index</title>
</head>
<body>
<div id="root">${indexHtml}</div>
<script src="/client.js"></script>
</body>
</html>
`
)
}Blog System Implementation:
// src/content/blogGenerator.ts
import fs from 'fs/promises'
import path from 'path'
import { renderToString } from 'react-dom/server'
import BlogPost from '../components/BlogPost'
import BlogIndex from '../components/BlogIndex'
import { loadMarkdownFile } from './loadMarkdown'
export async function generateBlogSite() {
const blogDir = path.join(process.cwd(), 'content/blog')
const files = await fs.readdir(blogDir)
// 1. Generate individual blog posts
const posts = await Promise.all(
files.map(async (file) => {
const filePath = path.join(blogDir, file)
const { html: markdownHtml, frontMatter } = await loadMarkdownFile(filePath)
const postHtml = renderToString(
<BlogPost content={markdownHtml} meta={frontMatter} />
)
const outputPath = path.join(
process.cwd(),
'dist',
'blog',
frontMatter.slug || file.replace('.md', '.html')
)
await fs.mkdir(path.dirname(outputPath), { recursive: true })
await fs.writeFile(outputPath, `
<!DOCTYPE html>
<html>
<head>
<title>${frontMatter.title || 'Blog Post'}</title>
</head>
<body>
<div id="root">${postHtml}</div>
<script src="/client.js"></script>
</body>
</html>
`)
return {
slug: frontMatter.slug || file.replace('.md', ''),
title: frontMatter.title,
date: frontMatter.date,
excerpt: markdownHtml.substring(0, 150) + '...'
}
})
)
// 2. Sort by date
posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
// 3. Generate blog index page
const indexHtml = renderToString(
<BlogIndex posts={posts} />
)
await fs.writeFile(
path.join(process.cwd(), 'dist', 'blog', 'index.html'),
`
<!DOCTYPE html>
<html>
<head>
<title>Blog</title>
</head>
<body>
<div id="root">${indexHtml}</div>
<script src="/client.js"></script>
</body>
</html>
`
)
// 4. Generate RSS feed
const rssFeed = generateRssFeed(posts)
await fs.writeFile(
path.join(process.cwd(), 'dist', 'blog', 'rss.xml'),
rssFeed
)
}
function generateRssFeed(posts: Array<{ slug: string; title: string; date: string; excerpt: string }>) {
return `
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Blog</title>
<link>https://example.com/blog</link>
<description>Latest blog posts</description>
${posts.map(post => `
<item>
<title>${post.title}</title>
<link>https://example.com/blog/${post.slug}</link>
<description>${post.excerpt}</description>
<pubDate>${post.date}</pubDate>
</item>
`).join('')}
</channel>
</rss>
`
}Real-Time Application Development
WebSocket Real-Time Communication
Bun.js WebSocket Server Implementation:
// src/server/websocket.ts
import { serve } from 'bun'
const server = serve({
port: 3000,
fetch(request) {
// Upgrade HTTP connection to WebSocket
if (request.headers.get('upgrade')?.toLowerCase() === 'websocket') {
const { socket, response } = Bun.upgradeWebSocket(request)
// WebSocket event handlers
socket.onopen = () => {
console.log('WebSocket connected')
socket.send(JSON.stringify({ type: 'WELCOME', message: 'Connected to Bun WebSocket' }))
}
socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
handleMessage(socket, data)
} catch (err) {
console.error('WebSocket message error:', err)
}
}
socket.onclose = () => {
console.log('WebSocket disconnected')
}
socket.onerror = (error) => {
console.error('WebSocket error:', error)
}
return response
}
// Regular HTTP request handling
return new Response('HTTP Server', { status: 200 })
}
})
// Message handling logic
function handleMessage(socket: WebSocket, data: any) {
switch (data.type) {
case 'CHAT_MESSAGE':
// Broadcast message to all clients
broadcastMessage({
type: 'CHAT_MESSAGE',
sender: data.sender,
content: data.content,
timestamp: Date.now()
})
break
case 'TYPING_INDICATOR':
// Send to specific room/user
sendToRoom(data.roomId, {
type: 'TYPING_INDICATOR',
userId: data.userId
})
break
}
}
// Broadcast message to all clients
function broadcastMessage(message: any) {
// In real applications, maintain client list
// Simplified example
console.log('Broadcasting message:', message)
}
// Send message to specific room
function sendToRoom(roomId: string, message: any) {
console.log(`Sending to room ${roomId}:`, message)
}
console.log('WebSocket Server running on ws://localhost:3000')Client Implementation:
// src/client/websocketClient.ts
export class WebSocketClient {
private socket: WebSocket | null = null
private messageHandlers: Array<(data: any) => void> = []
connect(url: string) {
this.socket = new WebSocket(url)
this.socket.onopen = () => {
console.log('WebSocket connected')
}
this.socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
this.messageHandlers.forEach(handler => handler(data))
} catch (err) {
console.error('Message parse error:', err)
}
}
this.socket.onclose = () => {
console.log('WebSocket disconnected')
// Optional: Implement auto-reconnect
setTimeout(() => this.connect(url), 5000)
}
this.socket.onerror = (error) => {
console.error('WebSocket error:', error)
}
}
send(data: any) {
if (this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(data))
} else {
console.error('WebSocket not connected')
}
}
onMessage(handler: (data: any) => void) {
this.messageHandlers.push(handler)
return () => {
this.messageHandlers = this.messageHandlers.filter(h => h !== handler)
}
}
}
// Usage example
const wsClient = new WebSocketClient()
wsClient.connect('ws://localhost:3000')
wsClient.onMessage((data) => {
if (data.type === 'CHAT_MESSAGE') {
console.log('New message:', data.content)
}
})
// Send message
wsClient.send({
type: 'CHAT_MESSAGE',
sender: 'user123',
content: 'Hello from Bun!'
})GraphQL and Bun.js Integration
Apollo Server Integration:
// src/server/graphql.ts
import { serve } from 'bun'
import { ApolloServer } from '@apollo/server'
import { startStandaloneServer } from '@apollo/server/standalone'
import { typeDefs } from './schema'
import { resolvers } from './resolvers'
async function startGraphQLServer() {
const server = new ApolloServer({
typeDefs,
resolvers
})
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 }
})
console.log(`GraphQL server ready at ${url}`)
}
// Run both HTTP and GraphQL servers
serve({
port: 3000,
fetch(request) {
// HTTP request handling...
}
})
startGraphQLServer()Native GraphQL Implementation with Bun:
// src/server/nativeGraphQL.ts
import { serve } from 'bun'
// Simple GraphQL type definition
type User = {
id: string
name: string
email: string
}
// Simulated database
const users: User[] = [
{ id: '1', name: 'Alice', email: 'alice@example.com' },
{ id: '2', name: 'Bob', email: 'bob@example.com' }
]
// GraphQL resolvers
const resolvers = {
Query: {
users: () => users,
user: (_: any, { id }: { id: string }) =>
users.find(user => user.id === id) || null
},
Mutation: {
createUser: (_: any, { name, email }: { name: string; email: string }) => {
const newUser = { id: Date.now().toString(), name, email }
users.push(newUser)
return newUser
}
}
}
// GraphQL schema
const typeDefs = `
type User {
id: ID!
name: String!
email: String!
}
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!, email: String!): User!
}
`
// Simple GraphQL request handler
async function handleGraphQLRequest(request: Request) {
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 })
}
try {
const { query, variables } = await request.json()
// Simplified GraphQL execution (use full parser in real projects)
if (query.includes('users')) {
return new Response(JSON.stringify({ data: { users } }), {
headers: { 'Content-Type': 'application/json' }
})
}
if (query.includes('user')) {
const id = query.match(/id:\s*"([^"]+)"/)?.[1]
const user = users.find(u => u.id === id)
return new Response(JSON.stringify({ data: { user } }), {
headers: { 'Content-Type': 'application/json' }
})
}
if (query.includes('createUser')) {
const name = query.match(/name:\s*"([^"]+)"/)?.[1]
const email = query.match(/email:\s*"([^"]+)"/)?.[1]
const newUser = resolvers.Mutation.createUser(null, { name, email })
return new Response(JSON.stringify({ data: { createUser: newUser } }), {
headers: { 'Content-Type': 'application/json' }
})
}
return new Response(JSON.stringify({ errors: [{ message: 'Invalid query' }] }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
})
} catch (err) {
console.error('GraphQL error:', err)
return new Response(JSON.stringify({ errors: [{ message: 'Internal Server Error' }] }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
})
}
}
const server = serve({
port: 4000,
fetch: handleGraphQLRequest
})
console.log('Native GraphQL Server running on http://localhost:4000')Real-Time Collaborative Editing
CRDT Implementation Basics:
// src/collaboration/crdt.ts
type Character = {
id: string // Unique identifier (includes site ID and counter)
value: string
position: number // Logical position
}
export class CRDTDocument {
private characters: Character[] = []
private siteId: string
private counter: number = 0
constructor(siteId: string) {
this.siteId = siteId
}
// Generate unique character ID
private generateId(): string {
return `${this.siteId}-${this.counter++}`
}
// Local character insertion
insert(value: string, position: number) {
const id = this.generateId()
const char: Character = { id, value, position }
// Find insertion position
let insertIndex = this.characters.findIndex(c => c.position >= position)
if (insertIndex === -1) {
insertIndex = this.characters.length
}
this.characters.splice(insertIndex, 0, char)
}
// Local character deletion
delete(position: number) {
const index = this.characters.findIndex(c => c.position === position)
if (index !== -1) {
this.characters.splice(index, 1)
}
}
// Apply remote operation
applyRemoteOperation(char: Character) {
// Check if character with same ID exists
const existingIndex = this.characters.findIndex(c => c.id === char.id)
if (existingIndex !== -1) {
// Update existing character
this.characters[existingIndex] = char
} else {
// Insert new character
let insertIndex = this.characters.findIndex(c => c.position >= char.position)
if (insertIndex === -1) {
insertIndex = this.characters.length
}
this.characters.splice(insertIndex, 0, char)
}
}
// Get document content
getContent(): string {
return this.characters.map(c => c.value).join('')
}
}
// Usage example
const doc1 = new CRDTDocument('site1')
const doc2 = new CRDTDocument('site2')
doc1.insert('H', 0)
doc1.insert('e', 1)
doc1.insert('l', 2)
doc1.insert('l', 3)
doc1.insert('o', 4)
// Simulate network transmission: sync doc1 operations to doc2
doc1.characters.forEach(char => {
doc2.applyRemoteOperation(char)
})
console.log('Doc1 content:', doc1.getContent()) // Output: Hello
console.log('Doc2 content:', doc2.getContent()) // Output: Hello
// Concurrent editing example
// Site 1 inserts 'X' at position 1
doc1.insert('X', 1)
console.log('After doc1 insert X:', doc1.getContent()) // Output: HXello
// Site 2 inserts 'Y' at position 2
doc2.insert('Y', 2)
console.log('After doc2 insert Y:', doc2.getContent()) // Output: HeYllo
// Sync doc2 operations to doc1
doc2.characters.forEach(char => {
// Avoid reapplying existing characters
if (!doc1.characters.some(c => c.id === char.id)) {
doc1.applyRemoteOperation(char)
}
})
console.log('After sync, doc1 content:', doc1.getContent()) // Output: HXYello
console.log('After sync, doc2 content:', doc2.getContent()) // Output: HeYlloComplete Real-Time Collaborative Editing Implementation:
// src/collaboration/realtimeEditor.ts
import { serve } from 'bun'
import { CRDTDocument } from './crdt'
import { WebSocketClient } from '../client/websocketClient'
type EditorState = {
document: CRDTDocument
clients: Set<WebSocketClientWrapper>
}
class RealtimeEditorServer {
private editors: Map<string, EditorState> = new Map() // documentId -> editor state
constructor() {
this.startServer()
}
private startServer() {
const server = serve({
port: 3000,
fetch: async (request) => {
// WebSocket upgrade handling
if (request.headers.get('upgrade')?.toLowerCase() === 'websocket') {
const { socket, response } = Bun.upgradeWebSocket(request)
await this.handleWebSocketConnection(socket)
return response
}
// HTTP API endpoint (optional)
return new Response('Realtime Editor Server', { status: 200 })
}
})
console.log('Realtime Editor Server running on ws://localhost:3000')
}
private async handleWebSocketConnection(socket: WebSocket) {
console.log('New client connected')
// 1. Client handshake (get document ID)
socket.onmessage = async (event) => {
try {
const message = JSON.parse(event.data)
if (message.type === 'JOIN_DOCUMENT') {
const { documentId } = message
// Get or create editor state
let editorState = this.editors.get(documentId)
if (!editorState) {
editorState = {
document: new CRDTDocument(this.generateSiteId()),
clients: new Set()
}
this.editors.set(documentId, editorState)
}
// 2. Add client to editor
editorState.clients.add(new WebSocketClientWrapper(socket))
// 3. Send current document state to new client
const currentState = editorState.document.getContent()
socket.send(JSON.stringify({
type: 'DOCUMENT_STATE',
content: currentState
}))
// 4. Notify other clients of new user
this.broadcastToOthers(editorState, {
type: 'USER_JOINED',
userId: socket.id
}, socket)
}
if (message.type === 'EDIT_OPERATION') {
const { documentId, operation } = message
const editorState = this.editors.get(documentId)
if (editorState) {
// Apply operation to CRDT document
if (operation.type === 'INSERT') {
editorState.document.insert(operation.value, operation.position)
} else if (operation.type === 'DELETE') {
editorState.document.delete(operation.position)
}
// Broadcast operation to other clients
this.broadcastToOthers(editorState, message, socket)
}
}
} catch (err) {
console.error('WebSocket message error:', err)
}
}
socket.onclose = () => {
console.log('Client disconnected')
// Clean up client references (needs improvement: find client's editor)
}
}
private broadcastToOthers(editorState: EditorState, message: any, excludeSocket: WebSocket) {
editorState.clients.forEach(client => {
if (client.socket !== excludeSocket && client.socket.readyState === WebSocket.OPEN) {
client.socket.send(JSON.stringify(message))
}
})
}
private generateSiteId(): string {
return Math.random().toString(36).substring(2, 10)
}
}
// WebSocket client wrapper (simplified)
class WebSocketClientWrapper {
constructor(public socket: WebSocket) {}
}
// Start server
new RealtimeEditorServer()Client-Side Collaborative Editing Integration:
// src/client/collaborativeEditor.ts
import { WebSocketClient } from './websocketClient'
import { CRDTDocument } from '../server/collaboration/crdt'
export class CollaborativeEditor {
private document: CRDTDocument
private wsClient: WebSocketClient
private documentId: string
constructor(documentId: string) {
this.documentId = documentId
this.document = new CRDTDocument(this.generateClientId())
this.wsClient = new WebSocketClient()
this.connect()
this.setupEventListeners()
}
private connect() {
this.wsClient.connect('ws://localhost:3000')
this.wsClient.onMessage((message) => {
if (message.type === 'DOCUMENT_STATE') {
// Initialize local document state
this.document = new CRDTDocument(this.generateClientId())
// Simplified: Implement logic to rebuild CRDT document from string
console.log('Received document state:', message.content)
}
if (message.type === 'EDIT_OPERATION') {
// Apply remote operation to local document
if (message.operation.type === 'INSERT') {
this.document.insert(message.operation.value, message.operation.position)
} else if (message.operation.type === 'DELETE') {
this.document.delete(message.operation.position)
}
this.updateUI()
}
})
}
private setupEventListeners() {
// Listen for local edit events (e.g., input changes)
document.getElementById('editor')?.addEventListener('input', (e) => {
const target = e.target as HTMLInputElement
const newValue = target.value
// Simplified diff calculation (use more precise algorithm in real projects)
const oldLength = this.document.getContent().length
const newLength = newValue.length
if (newLength > oldLength) {
// Insert operation
const insertedText = newValue.substring(oldLength)
this.document.insert(insertedText, oldLength)
// Send operation to server
this.wsClient.send({
type: 'EDIT_OPERATION',
documentId: this.documentId,
operation: {
type: 'INSERT',
value: insertedText,
position: oldLength
}
})
} else if (newLength < oldLength) {
// Delete operation
const deletedCount = oldLength - newLength
this.document.delete(oldLength - 1) // Simplified: delete last character
// Send operation to server
this.wsClient.send({
type: 'EDIT_OPERATION',
documentId: this.documentId,
operation: {
type: 'DELETE',
position: oldLength - 1
}
})
}
})
}
private updateUI() {
// Update editor UI display
const editorElement = document.getElementById('editor') as HTMLInputElement
if (editorElement) {
editorElement.value = this.document.getContent()
}
}
private generateClientId(): string {
return Math.random().toString(36).substring(2, 10)
}
}
// Usage example
new CollaborativeEditor('doc-123')Real-Time Application Performance Optimization
WebSocket Message Compression:
// src/server/compressedWebSocket.ts
import { serve } from 'bun'
import pako from 'pako' // zlib compression library
const server = serve({
port: 3000,
fetch(request) {
if (request.headers.get('upgrade')?.toLowerCase() === 'websocket') {
const { socket, response } = Bun.upgradeWebSocket(request)
const compressedSocket = {
send(data: any) {
// Compress data
const jsonStr = JSON.stringify(data)
const compressed = pako.deflate(jsonStr)
const base64 = btoa(String.fromCharCode(...compressed))
socket.send(base64)
},
onmessage(event) {
// Decompress data
const base64 = event.data
const binaryString = atob(base64)
const bytes = new Uint8Array(binaryString.length)
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
const decompressed = pako.inflate(bytes)
const jsonStr = new TextDecoder().decode(decompressed)
const data = JSON.parse(jsonStr)
// Trigger original onmessage event
if (socket.onmessage) {
socket.onmessage({ data })
}
}
}
// Replace original socket methods
socket.send = compressedSocket.send.bind(compressedSocket)
// ...other WebSocket event handling...
}
return new Response('Compressed WebSocket Server', { status: 200 })
}
})
console.log('Compressed WebSocket Server running on ws://localhost:3000')Message Batching and Throttling:
// src/client/batchedWebSocket.ts
export class BatchedWebSocketClient {
private socket: WebSocket
private messageQueue: Array<{ type: string; data: any }> = []
private batchInterval: number
private isBatching: boolean = false
constructor(url: string, batchInterval: number = 50) {
this.socket = new WebSocket(url)
this.batchInterval = batchInterval
this.socket.onopen = () => {
this.startBatching()
}
}
private startBatching() {
setInterval(() => {
if (this.messageQueue.length > 0) {
this.flushBatch()
}
}, this.batchInterval)
}
private flushBatch() {
if (this.messageQueue.length === 0) return
const batch = this.messageQueue.splice(0, this.messageQueue.length)
this.socket.send(JSON.stringify({
type: 'BATCH',
messages: batch
}))
}
send(type: string, data: any) {
this.messageQueue.push({ type, data })
// Flush immediately if queue reaches threshold
if (this.messageQueue.length >= 10) {
this.flushBatch()
}
}
onMessage(handler: (data: any) => void) {
this.socket.onmessage = (event) => {
const message = JSON.parse(event.data)
if (message.type === 'BATCH') {
message.messages.forEach(msg => handler(msg))
} else {
handler(message)
}
}
}
}
// Usage example
const client = new BatchedWebSocketClient('ws://localhost:3000')
client.send('CURSOR_MOVE', { x: 100, y: 200 })
client.send('SELECTION_CHANGE', { start: 0, end: 10 })
// Batch sending triggered automatically after 50ms intervalReal-Time Application Security and Access Control
WebSocket Authentication and Authorization:
// src/server/secureWebSocket.ts
import { serve } from 'bun'
const server = serve({
port: 3000,
fetch(request) {
if (request.headers.get('upgrade')?.toLowerCase() === 'websocket') {
// 1. HTTP-level authentication
const authToken = request.headers.get('sec-websocket-protocol')
if (!validateToken(authToken)) {
return new Response('Unauthorized', { status: 401 })
}
// 2. Upgrade connection
const { socket, response } = Bun.upgradeWebSocket(request, {
protocol: authToken // Pass auth token
})
// 3. WebSocket-level permission control
socket.onmessage = (event) => {
try {
const message = JSON.parse(event.data)
// Check if user has permission for operation
if (!checkPermission(authToken, message.type)) {
socket.send(JSON.stringify({
type: 'ERROR',
message: 'Permission denied'
}))
return
}
// Handle message...
} catch (err) {
console.error('WebSocket error:', err)
}
}
return response
}
return new Response('Secure WebSocket Server', { status: 200 })
}
})
// Simulated authentication validation
function validateToken(token: string | null): boolean {
return token === 'valid-token' // Use JWT or other standard in real projects
}
// Simulated permission check
function checkPermission(token: string | null, operation: string): boolean {
if (!token) return false
const userPermissions = {
'valid-token': ['READ', 'WRITE'] // Example permissions
}
return userPermissions[token]?.includes(operation) || false
}
console.log('Secure WebSocket Server running on ws://localhost:3000')Rate Limiting Implementation:
// src/middleware/rateLimiter.ts
import { Context, Next } from 'bun'
type RateLimitConfig = {
windowMs: number // Time window (milliseconds)
maxRequests: number // Maximum requests
}
const rateLimiters = new Map<string, { count: number; resetTime: number }>()
export function rateLimiter(config: RateLimitConfig) {
return async (ctx: Context, next: Next) => {
const clientIp = ctx.request.ip || 'unknown'
const now = Date.now()
if (!rateLimiters.has(clientIp)) {
rateLimiters.set(clientIp, {
count: 1,
resetTime: now + config.windowMs
})
} else {
const limiter = rateLimiters.get(clientIp)!
if (now > limiter.resetTime) {
// Reset counter
limiter.count = 1
limiter.resetTime = now + config.windowMs
} else {
// Increment counter
limiter.count++
if (limiter.count > config.maxRequests) {
ctx.response.status = 429
ctx.response.body = 'Too Many Requests'
return
}
}
}
await next()
}
}
// Usage example
server.use(rateLimiter({
windowMs: 10 * 1000, // 10-second window
maxRequests: 100 // Max 100 requests
}))Data Validation and Sanitization:
// src/utils/sanitization.ts
import DOMPurify from 'dompurify' // HTML sanitization library
export function sanitizeInput(input: string, type: 'text' | 'html' | 'url') {
switch (type) {
case 'html':
// Sanitize HTML to prevent XSS
return DOMPurify.sanitize(input)
case 'url':
// Validate URL format
try {
new URL(input)
return input
} catch {
throw new Error('Invalid URL format')
}
case 'text':
default:
// Basic text sanitization
return input.replace(/[ {
try {
const message = JSON.parse(event.data)
// Sanitize user input
if (message.type === 'CHAT_MESSAGE') {
message.content = sanitizeInput(message.content, 'html')
}
// Process sanitized message...
} catch (err) {
console.error('Message processing error:', err)
}
}Summary
Key points for Bun.js advanced application development:
- Server-Side Rendering (SSR):
- Leverage Bun’s fast startup for instant page rendering
- Use streaming rendering to improve first-paint performance
- Implement component-level caching to reduce redundant computation
- Static Site Generation (SSG):
- Best practices for build-time static file generation
- Automation for dynamic routing and content management
- Seamless integration with CMS systems
- Real-Time Application Development:
- Hybrid WebSocket and GraphQL communication models
- CRDT algorithms for conflict-free collaborative editing
- Comprehensive performance optimization and security solutions
Architecture Recommendations:
- Use SSR for SEO-sensitive pages requiring fast first paint
- Adopt SSG with incremental regeneration for content-stable pages
- Implement WebSocket + CRDT for high-realtime requirements
- Use hybrid architectures to leverage the strengths of different technologies
By leveraging Bun.js’s high-performance capabilities, developers can build modern web applications that offer both excellent user experiences and high development efficiency.



