🎯 empfohlene Sammlungen
Balanced sample collections from various categories for you to explore
Fresh Beispiele
Fresh Full-Stack Web-Framework-Beispiele einschließlich Islands, Routing, Middleware und Deployment
💻 Fresh Hello World typescript
🟢 simple
Grundkonfiguration des Fresh-Frameworks und Hello World-Anwendung
// Fresh Hello World Examples
// 1. Basic Fresh app structure
// main.ts
import { Fresh } from '$fresh/server.ts'
import { twind } from '$fresh/twind.ts'
import manifest from './fresh.gen.ts'
await Fresh(manifest, { plugins: [twind()] })
// routes/index.tsx
import { PageProps } from '$fresh/server.ts'
export default function Home(props: PageProps) {
return (
<div>
<h1>Hello, World!</h1>
<p>Welcome to Fresh framework</p>
</div>
)
}
// 2. Fresh with different routes
// routes/about.tsx
import { PageProps } from '$fresh/server.ts'
export default function About() {
return (
<div>
<h1>About Fresh</h1>
<p>Fresh is a full-stack web framework for Deno</p>
</div>
)
}
// routes/users/[name].tsx
import { PageProps } from '$fresh/server.ts'
export default function UserProfile(props: PageProps) {
const name = props.params.name
return (
<div>
<h1>Hello, {name}!</h1>
<p>This is your profile page</p>
</div>
)
}
// 3. Fresh with API routes
// routes/api/hello.ts
import { Handlers } from '$fresh/server.ts'
export const handler: Handlers = {
GET(req) {
const url = new URL(req.url)
const name = url.searchParams.get('name') || 'World'
return new Response(`Hello, ${name}!`)
},
POST(req) {
return new Response(JSON.stringify({
message: 'Hello from Fresh API!',
timestamp: new Date().toISOString()
}), {
headers: { 'Content-Type': 'application/json' }
})
}
}
// 4. Fresh with Islands for interactivity
// islands/Counter.tsx
import { useState } from 'preact/hooks'
interface CounterProps {
initial?: number
}
export default function Counter({ initial = 0 }: CounterProps) {
const [count, setCount] = useState(initial)
return (
<div>
<h2>Counter: {count}</h2>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(initial)}>Reset</button>
</div>
)
}
// routes/index.tsx (using the island)
import { PageProps } from '$fresh/server.ts'
import Counter from '../islands/Counter.tsx'
export default function Home(props: PageProps) {
return (
<div>
<h1>Hello, Fresh!</h1>
<p>Interactive Counter Island:</p>
<Counter initial={0} />
</div>
)
}
// 5. Fresh with layouts
// routes/_app.tsx
import { AppProps } from '$fresh/server.ts'
import { Head } from '$fresh/runtime.ts'
export default function App({ Component }: AppProps) {
return (
<>
<Head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Fresh App</title>
<link rel="stylesheet" href="/styles.css" />
</Head>
<Component />
</>
)
}
// routes/_layout.tsx
import { PageProps } from '$fresh/server.ts'
import Header from '../components/Header.tsx'
import Footer from '../components/Footer.tsx'
export default function Layout({ children }: PageProps) {
return (
<div className="layout">
<Header />
<main>{children}</main>
<Footer />
</div>
)
}
// 6. Fresh with middleware
// middleware.ts
import { MiddlewareHandler } from '$fresh/server.ts'
export const handler: MiddlewareHandler = async (req, ctx) => {
// Add custom headers
const response = await ctx.next()
response.headers.set('X-Fresh', 'true')
response.headers.set('X-Timestamp', new Date().toISOString())
return response
}
// routes/api/protected.ts
import { Handlers } from '$fresh/server.ts'
export const handler: Handlers = {
async GET(req, ctx) {
// Access middleware data
const middlewareData = ctx.state.middlewareData
return new Response(JSON.stringify({
message: 'This is a protected route',
timestamp: new Date().toISOString(),
middleware: middlewareData
}), {
headers: { 'Content-Type': 'application/json' }
})
}
}
// 7. Fresh with error handling
// routes/_500.tsx
import { ErrorProps } from '$fresh/server.ts'
export default function Error500({ error }: ErrorProps) {
return (
<div className="error-page">
<h1>500 - Internal Server Error</h1>
<p>Something went wrong</p>
<pre>{error.message}</pre>
<a href="/">Go back home</a>
</div>
)
}
// routes/_404.tsx
import { NotFoundProps } from '$fresh/server.ts'
export default function NotFound({ url }: NotFoundProps) {
return (
<div className="error-page">
<h1>404 - Not Found</h1>
<p>The page <code>{url}</code> was not found</p>
<a href="/">Go back home</a>
</div>
)
}
// 8. Fresh with static file serving
// static/styles.css
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
h1 {
color: #333;
text-align: center;
}
.layout {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
main {
min-height: calc(100vh - 200px);
}
.error-page {
text-align: center;
padding: 40px;
}
// Usage in components
// components/Header.tsx
export default function Header() {
return (
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/api/hello">API</a>
</nav>
</header>
)
}
// 9. Fresh with database integration
// lib/db.ts
interface User {
id: number
name: string
email: string
createdAt: Date
}
// In-memory database for demo
const users: User[] = [
{ id: 1, name: 'John Doe', email: '[email protected]', createdAt: new Date() },
{ id: 2, name: 'Jane Smith', email: '[email protected]', createdAt: new Date() }
]
export function getUsers(): User[] {
return users
}
export function getUserById(id: number): User | undefined {
return users.find(user => user.id === id)
}
export function createUser(user: Omit<User, 'id' | 'createdAt'>): User {
const newUser = {
id: users.length + 1,
...user,
createdAt: new Date()
}
users.push(newUser)
return newUser
}
// routes/api/users.ts
import { Handlers } from '$fresh/server.ts'
import { getUsers, createUser } from '../../lib/db.ts'
export const handler: Handlers = {
GET() {
const users = getUsers()
return new Response(JSON.stringify(users), {
headers: { 'Content-Type': 'application/json' }
})
},
async POST(req) {
const body = await req.json()
const user = createUser(body)
return new Response(JSON.stringify(user), {
status: 201,
headers: { 'Content-Type': 'application/json' }
})
}
}
// 10. Fresh with session management
// lib/session.ts
import { FreshContext } from '$fresh/server.ts'
export interface SessionData {
userId?: number
username?: string
role?: string
}
export async function getSession(ctx: FreshContext): Promise<SessionData> {
const sessionCookie = ctx.cookies.get('session')
if (!sessionCookie) {
return {}
}
// In production, use encrypted or signed cookies
try {
return JSON.parse(atob(sessionCookie))
} catch {
return {}
}
}
export async function setSession(ctx: FreshContext, data: SessionData) {
const sessionData = btoa(JSON.stringify(data))
ctx.cookies.set('session', sessionData, {
httpOnly: true,
secure: true,
sameSite: 'Strict',
maxAge: 7 * 24 * 60 * 60 // 7 days
})
}
export async function clearSession(ctx: FreshContext) {
ctx.cookies.set('session', '', {
httpOnly: true,
secure: true,
sameSite: 'Strict',
maxAge: 0
})
}
// routes/api/login.ts
import { Handlers } from '$fresh/server.ts'
import { setSession } from '../../lib/session.ts'
export const handler: Handlers = {
async POST(req, ctx) {
const { username, password } = await req.json()
// Mock authentication
if (username === 'admin' && password === 'password') {
await setSession(ctx, {
userId: 1,
username: 'admin',
role: 'admin'
})
return new Response(JSON.stringify({
success: true,
message: 'Login successful'
}), {
headers: { 'Content-Type': 'application/json' }
})
}
return new Response(JSON.stringify({
success: false,
message: 'Invalid credentials'
}), {
status: 401,
headers: { 'Content-Type': 'application/json' }
})
}
}
💻 Fresh Islands-Architektur typescript
🟡 intermediate
Interaktive Komponenten mit dem Fresh Islands-Pattern
// Fresh Islands Architecture Examples
// 1. Basic Island with State
// islands/ClickCounter.tsx
import { useState } from 'preact/hooks'
export default function ClickCounter() {
const [clicks, setClicks] = useState(0)
return (
<div class="counter">
<h2>Clicks: {clicks}</h2>
<button onClick={() => setClicks(clicks + 1)}>
Click me
</button>
<button onClick={() => setClicks(0)}>
Reset
</button>
</div>
)
}
// 2. Island with props and events
// islands/TodoItem.tsx
interface TodoItemProps {
text: string
completed: boolean
onToggle: () => void
onDelete: () => void
}
export default function TodoItem({ text, completed, onToggle, onDelete }: TodoItemProps) {
return (
<div class={`todo-item ${completed ? 'completed' : ''}`}>
<input
type="checkbox"
checked={completed}
onChange={onToggle}
/>
<span class={completed ? 'completed-text' : ''}>{text}</span>
<button onClick={onDelete} class="delete-btn">
❌
</button>
</div>
)
}
// islands/TodoList.tsx
import { useState } from 'preact/hooks'
import TodoItem from './TodoItem.tsx'
interface Todo {
id: number
text: string
completed: boolean
}
export default function TodoList() {
const [todos, setTodos] = useState<Todo[]>([
{ id: 1, text: 'Learn Fresh', completed: false },
{ id: 2, text: 'Build an app', completed: false }
])
const [inputValue, setInputValue] = useState('')
const addTodo = () => {
if (inputValue.trim()) {
setTodos([...todos, {
id: Date.now(),
text: inputValue,
completed: false
}])
setInputValue('')
}
}
const toggleTodo = (id: number) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
))
}
const deleteTodo = (id: number) => {
setTodos(todos.filter(todo => todo.id !== id))
}
return (
<div class="todo-list">
<h3>Todo List</h3>
<div class="add-todo">
<input
type="text"
value={inputValue}
onInput={(e) => setInputValue((e.target as HTMLInputElement).value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
placeholder="Add a new todo..."
/>
<button onClick={addTodo}>Add</button>
</div>
<div class="todos">
{todos.map(todo => (
<TodoItem
key={todo.id}
text={todo.text}
completed={todo.completed}
onToggle={() => toggleTodo(todo.id)}
onDelete={() => deleteTodo(todo.id)}
/>
))}
</div>
<p>
Total: {todos.length} |
Completed: {todos.filter(t => t.completed).length} |
Remaining: {todos.filter(t => !t.completed).length}
</p>
</div>
)
}
// 3. Island with async operations
// islands/UserSearch.tsx
import { useState } from 'preact/hooks'
interface User {
id: number
name: string
email: string
username: string
}
export default function UserSearch() {
const [query, setQuery] = useState('')
const [users, setUsers] = useState<User[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const searchUsers = async () => {
if (!query.trim()) {
setUsers([])
return
}
setLoading(true)
setError('')
try {
const response = await fetch(`/api/search/users?q=${encodeURIComponent(query)}`)
if (!response.ok) {
throw new Error('Search failed')
}
const data = await response.json()
setUsers(data)
} catch (err) {
setError(err.message)
setUsers([])
} finally {
setLoading(false)
}
}
return (
<div class="user-search">
<h3>User Search</h3>
<div class="search-input">
<input
type="text"
value={query}
onInput={(e) => setQuery((e.target as HTMLInputElement).value)}
onKeyPress={(e) => e.key === 'Enter' && searchUsers()}
placeholder="Search for users..."
/>
<button onClick={searchUsers} disabled={loading}>
{loading ? 'Searching...' : 'Search'}
</button>
</div>
{error && <div class="error">{error}</div>}
{users.length > 0 && (
<div class="results">
<h4>Results ({users.length})</h4>
<ul>
{users.map(user => (
<li key={user.id} class="user-result">
<strong>{user.name}</strong> (@{user.username})
<br />
<small>{user.email}</small>
</li>
))}
</ul>
</div>
)}
</div>
)
}
// 4. Island with forms and validation
// islands/ContactForm.tsx
import { useState } from 'preact/hooks'
interface FormData {
name: string
email: string
subject: string
message: string
}
interface FormErrors {
name?: string
email?: string
subject?: string
message?: string
}
export default function ContactForm() {
const [formData, setFormData] = useState<FormData>({
name: '',
email: '',
subject: '',
message: ''
})
const [errors, setErrors] = useState<FormErrors>({})
const [submitting, setSubmitting] = useState(false)
const [success, setSuccess] = useState(false)
const validateForm = (): boolean => {
const newErrors: FormErrors = {}
if (!formData.name.trim()) {
newErrors.name = 'Name is required'
}
if (!formData.email.trim()) {
newErrors.email = 'Email is required'
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = 'Invalid email format'
}
if (!formData.subject.trim()) {
newErrors.subject = 'Subject is required'
}
if (!formData.message.trim()) {
newErrors.message = 'Message is required'
} else if (formData.message.length < 10) {
newErrors.message = 'Message must be at least 10 characters'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = async (e: Event) => {
e.preventDefault()
if (!validateForm()) {
return
}
setSubmitting(true)
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
if (response.ok) {
setSuccess(true)
setFormData({ name: '', email: '', subject: '', message: '' })
} else {
throw new Error('Failed to send message')
}
} catch (err) {
alert('Error: ' + err.message)
} finally {
setSubmitting(false)
}
}
const handleChange = (field: keyof FormData, value: string) => {
setFormData({ ...formData, [field]: value })
if (errors[field]) {
setErrors({ ...errors, [field]: undefined })
}
}
if (success) {
return (
<div class="contact-form">
<div class="success-message">
<h3>Thank you!</h3>
<p>Your message has been sent successfully.</p>
<button onClick={() => setSuccess(false)}>Send another message</button>
</div>
</div>
)
}
return (
<form class="contact-form" onSubmit={handleSubmit}>
<h3>Contact Us</h3>
<div class="form-group">
<label htmlFor="name">Name *</label>
<input
type="text"
id="name"
value={formData.name}
onInput={(e) => handleChange('name', (e.target as HTMLInputElement).value)}
class={errors.name ? 'error' : ''}
/>
{errors.name && <span class="error-text">{errors.name}</span>}
</div>
<div class="form-group">
<label htmlFor="email">Email *</label>
<input
type="email"
id="email"
value={formData.email}
onInput={(e) => handleChange('email', (e.target as HTMLInputElement).value)}
class={errors.email ? 'error' : ''}
/>
{errors.email && <span class="error-text">{errors.email}</span>}
</div>
<div class="form-group">
<label htmlFor="subject">Subject *</label>
<input
type="text"
id="subject"
value={formData.subject}
onInput={(e) => handleChange('subject', (e.target as HTMLInputElement).value)}
class={errors.subject ? 'error' : ''}
/>
{errors.subject && <span class="error-text">{errors.subject}</span>}
</div>
<div class="form-group">
<label htmlFor="message">Message *</label>
<textarea
id="message"
value={formData.message}
onInput={(e) => handleChange('message', (e.target as HTMLTextAreaElement).value)}
class={errors.message ? 'error' : ''}
rows={5}
/>
{errors.message && <span class="error-text">{errors.message}</span>}
</div>
<button type="submit" disabled={submitting} class="submit-btn">
{submitting ? 'Sending...' : 'Send Message'}
</button>
</form>
)
}
// 5. Island with local storage
// islands/ThemeToggle.tsx
import { useState, useEffect } from 'preact/hooks'
export default function ThemeToggle() {
const [theme, setTheme] = useState<'light' | 'dark'>('light')
useEffect(() => {
// Load theme from localStorage
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null
if (savedTheme) {
setTheme(savedTheme)
document.documentElement.setAttribute('data-theme', savedTheme)
}
}, [])
const toggleTheme = () => {
const newTheme = theme === 'light' ? 'dark' : 'light'
setTheme(newTheme)
localStorage.setItem('theme', newTheme)
document.documentElement.setAttribute('data-theme', newTheme)
}
return (
<button onClick={toggleTheme} class="theme-toggle">
{theme === 'light' ? '🌙' : '☀️'} {theme === 'light' ? 'Dark' : 'Light'} Mode
</button>
)
}
// 6. Island with real-time data
// islands/Clock.tsx
import { useState, useEffect } from 'preact/hooks'
export default function Clock() {
const [time, setTime] = useState(new Date())
const [is24Hour, setIs24Hour] = useState(false)
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date())
}, 1000)
return () => clearInterval(timer)
}, [])
const formatTime = (date: Date) => {
if (is24Hour) {
return date.toLocaleTimeString('en-US', { hour12: false })
} else {
return date.toLocaleTimeString('en-US', { hour12: true })
}
}
const formatDate = (date: Date) => {
return date.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
return (
<div class="clock">
<div class="time">{formatTime(time)}</div>
<div class="date">{formatDate(time)}</div>
<button onClick={() => setIs24Hour(!is24Hour)}>
{is24Hour ? '12-hour' : '24-hour'} format
</button>
</div>
)
}
// 7. Island with drag and drop
// islands/FileUpload.tsx
import { useState } from 'preact/hooks'
interface UploadedFile {
name: string
size: number
type: string
uploadTime: Date
}
export default function FileUpload() {
const [files, setFiles] = useState<UploadedFile[]>([])
const [isDragging, setIsDragging] = useState(false)
const [uploading, setUploading] = useState(false)
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
setIsDragging(true)
}
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
setIsDragging(false)
}
const handleDrop = (e: DragEvent) => {
e.preventDefault()
setIsDragging(false)
const droppedFiles = Array.from(e.dataTransfer?.files || [])
processFiles(droppedFiles)
}
const handleFileSelect = (e: Event) => {
const input = e.target as HTMLInputElement
const selectedFiles = Array.from(input.files || [])
processFiles(selectedFiles)
}
const processFiles = async (fileList: File[]) => {
setUploading(true)
const uploadedFiles: UploadedFile[] = fileList.map(file => ({
name: file.name,
size: file.size,
type: file.type,
uploadTime: new Date()
}))
// Simulate upload delay
await new Promise(resolve => setTimeout(resolve, 1000))
setFiles([...files, ...uploadedFiles])
setUploading(false)
}
const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
return (
<div class="file-upload">
<h3>File Upload</h3>
<div
class={`drop-zone ${isDragging ? 'dragging' : ''}`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
{uploading ? (
<div class="uploading">
<div class="spinner"></div>
<p>Uploading files...</p>
</div>
) : (
<div class="upload-prompt">
<p>Drag and drop files here, or</p>
<label for="file-input" class="file-input-label">
choose files
</label>
<input
id="file-input"
type="file"
multiple
onChange={handleFileSelect}
style={{ display: 'none' }}
/>
</div>
)}
</div>
{files.length > 0 && (
<div class="file-list">
<h4>Uploaded Files ({files.length})</h4>
<ul>
{files.map((file, index) => (
<li key={index} class="file-item">
<div class="file-info">
<span class="file-name">{file.name}</span>
<span class="file-size">{formatFileSize(file.size)}</span>
</div>
<span class="file-time">
{file.uploadTime.toLocaleTimeString()}
</span>
</li>
))}
</ul>
</div>
)}
</div>
)
}
💻 Fresh Routing und Middleware typescript
🟡 intermediate
Erweiterte Routing-Patterns und Middleware-Implementierung in Fresh
// Fresh Routing and Middleware Examples
// 1. Dynamic routes with parameters
// routes/users/[id].tsx
import { PageProps, Handlers } from '$fresh/server.ts'
import { getUserById } from '../../lib/db.ts'
export const handler: Handlers = {
async GET(_req, ctx) {
const id = parseInt(ctx.params.id)
const user = getUserById(id)
if (!user) {
return new Response('User not found', { status: 404 })
}
return ctx.render({ user })
}
}
export default function UserPage({ data }: PageProps<{ user: any }>) {
return (
<div>
<h1>User Profile</h1>
<div class="user-card">
<h2>{data.user.name}</h2>
<p>Email: {data.user.email}</p>
<p>Created: {data.user.createdAt.toLocaleDateString()}</p>
</div>
</div>
)
}
// 2. Catch-all routes
// routes/docs/[...path].tsx
import { PageProps } from '$fresh/server.ts'
export default function DocsPage(props: PageProps) {
const path = props.params.path || []
return (
<div class="docs">
<nav class="docs-nav">
<a href="/docs/getting-started">Getting Started</a>
<a href="/docs/components">Components</a>
<a href="/docs/api">API Reference</a>
</nav>
<main class="docs-content">
<h1>Documentation</h1>
<p>Current path: /docs/{path.join('/')}</p>
{path.length === 0 && (
<div>
<h2>Welcome to the documentation</h2>
<p>Select a topic from the navigation.</p>
</div>
)}
{path[0] === 'getting-started' && (
<div>
<h2>Getting Started</h2>
<p>Learn how to get started with Fresh...</p>
</div>
)}
{path[0] === 'components' && (
<div>
<h2>Components</h2>
<p>Learn about Fresh components...</p>
{path[1] === 'islands' && (
<div>
<h3>Islands</h3>
<p>Islands are interactive components...</p>
</div>
)}
</div>
)}
</main>
</div>
)
}
// 3. Route-specific middleware
// middleware.ts
import { MiddlewareHandler } from '$fresh/server.ts'
// CORS middleware
export const corsMiddleware: MiddlewareHandler = async (req, ctx) => {
const response = await ctx.next()
response.headers.set('Access-Control-Allow-Origin', '*')
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')
return response
}
// Authentication middleware
export const authMiddleware: MiddlewareHandler = async (req, ctx) => {
const authHeader = req.headers.get('Authorization')
if (!authHeader?.startsWith('Bearer ')) {
return new Response('Unauthorized', { status: 401 })
}
const token = authHeader.slice(7)
// Validate token (mock implementation)
if (token !== 'valid-token') {
return new Response('Invalid token', { status: 401 })
}
// Store user info in context
ctx.state.user = { id: 1, name: 'John Doe', role: 'admin' }
return await ctx.next()
}
// Rate limiting middleware
const requestCounts = new Map<string, { count: number; resetTime: number }>()
export const rateLimitMiddleware: MiddlewareHandler = async (req, ctx) => {
const clientIp = req.headers.get('x-forwarded-for') || '127.0.0.1'
const now = Date.now()
const windowMs = 60000 // 1 minute
const maxRequests = 100
const record = requestCounts.get(clientIp)
if (!record || now > record.resetTime) {
requestCounts.set(clientIp, { count: 1, resetTime: now + windowMs })
return await ctx.next()
}
if (record.count >= maxRequests) {
return new Response('Too Many Requests', { status: 429 })
}
record.count++
return await ctx.next()
}
// Apply middleware globally in main.ts
import { Fresh } from '$fresh/server.ts'
import { corsMiddleware, rateLimitMiddleware } from './middleware.ts'
import manifest from './fresh.gen.ts'
await Fresh(manifest, {
plugins: [
corsMiddleware,
rateLimitMiddleware
]
})
// 4. Route-specific middleware
// routes/api/admin/[...path].ts
import { Handlers } from '$fresh/server.ts'
import { authMiddleware } from '../../../middleware.ts'
export const handler: Handlers = {
GET: authMiddleware(async (req, ctx) => {
const user = ctx.state.user
if (user.role !== 'admin') {
return new Response('Forbidden', { status: 403 })
}
return new Response(JSON.stringify({
message: 'Admin data',
user: user
}), {
headers: { 'Content-Type': 'application/json' }
})
})
}
// 5. Conditional middleware
// middleware.ts
export const conditionalAuth = (condition: (req: Request) => boolean): MiddlewareHandler => {
return async (req, ctx) => {
if (condition(req)) {
return await authMiddleware(req, ctx)
}
return await ctx.next()
}
}
// Usage in route
// routes/profile.tsx
import { Handlers, PageProps } from '$fresh/server.ts'
import { conditionalAuth } from '../../middleware.ts'
export const handler: Handlers = {
GET: conditionalAuth((req) => {
// Apply auth only if Authorization header is present
return req.headers.has('Authorization')
})(async (req, ctx) => {
const user = ctx.state.user
return ctx.render({
isAuthenticated: !!user,
user
})
})
}
export default function Profile({ data }: PageProps) {
return (
<div>
<h1>Profile</h1>
{data.isAuthenticated ? (
<div>
<p>Welcome, {data.user.name}!</p>
<p>Role: {data.user.role}</p>
</div>
) : (
<div>
<p>You are not logged in.</p>
<a href="/login">Login</a>
</div>
)}
</div>
)
}
// 6. Route guards with types
// types/auth.ts
export interface AuthState {
user: {
id: number
name: string
email: string
role: 'admin' | 'user'
}
}
// routes/api/guarded/[resource].ts
import { Handlers } from '$fresh/server.ts'
import type { AuthState } from '../../../types/auth.ts'
export const handler: Handlers = {
GET: async (req, ctx) => {
const user = (ctx.state as AuthState)?.user
if (!user) {
return new Response('Unauthorized', { status: 401 })
}
const resource = ctx.params.resource
const userRole = user.role
// Check permissions
if (resource === 'admin' && userRole !== 'admin') {
return new Response('Forbidden', { status: 403 })
}
return new Response(JSON.stringify({
message: `Access granted to ${resource}`,
user: user.name
}), {
headers: { 'Content-Type': 'application/json' }
})
}
}
// 7. Route versioning
// routes/v1/api/users.ts
import { Handlers } from '$fresh/server.ts'
export const handler: Handlers = {
GET() {
return new Response(JSON.stringify({
version: 'v1',
users: [
{ id: 1, name: 'John Doe', email: '[email protected]' }
]
}), {
headers: {
'Content-Type': 'application/json',
'API-Version': 'v1'
}
})
}
}
// routes/v2/api/users.ts
import { Handlers } from '$fresh/server.ts'
export const handler: Handlers = {
GET() {
return new Response(JSON.stringify({
version: 'v2',
users: [
{ id: 1, name: 'John Doe', email: '[email protected]', profile: { age: 30 } },
{ id: 2, name: 'Jane Smith', email: '[email protected]', profile: { age: 25 } }
],
metadata: {
total: 2,
page: 1,
limit: 10
}
}), {
headers: {
'Content-Type': 'application/json',
'API-Version': 'v2'
}
})
}
}
// routes/api/users.ts (latest version)
import { Handlers } from '$fresh/server.ts'
export const handler: Handlers = {
GET(req) {
const url = new URL(req.url)
const version = url.searchParams.get('version') || 'v2'
// Redirect to appropriate version
if (version === 'v1') {
return Response.redirect(new URL('/v1/api/users', req.url))
}
// Default to v2
return Response.redirect(new URL('/v2/api/users', req.url))
}
}
// 8. Route groups with shared middleware
// routes/api/_middleware.ts
import { MiddlewareHandler } from '$fresh/server.ts'
export const handler: MiddlewareHandler = async (req, ctx) => {
// All API routes will have this middleware
console.log(`API Request: ${req.method} ${req.url}`)
const response = await ctx.next()
// Add common API headers
response.headers.set('Content-Type', 'application/json')
response.headers.set('X-API-Version', '1.0')
return response
}
// routes/api/users/index.ts
import { Handlers } from '$fresh/server.ts'
export const handler: Handlers = {
GET() {
return new Response(JSON.stringify({ users: [] }))
},
POST(req) {
return new Response(JSON.stringify({
message: 'User created',
status: 'success'
}), { status: 201 })
}
}
// 9. Route validation middleware
// middleware/validation.ts
import { z } from 'zod'
export function validateBody(schema: z.ZodSchema): MiddlewareHandler {
return async (req, ctx) => {
if (req.method === 'GET') {
return await ctx.next()
}
try {
const body = await req.json()
const validated = schema.parse(body)
// Store validated body in context
ctx.state.validatedBody = validated
return await ctx.next()
} catch (error) {
if (error instanceof z.ZodError) {
return new Response(JSON.stringify({
error: 'Validation failed',
details: error.errors
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
})
}
throw error
}
}
}
// Usage in route
// routes/api/users.ts
import { Handlers } from '$fresh/server.ts'
import { validateBody } from '../../middleware/validation.ts'
import { z } from 'zod'
const userSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().min(18).optional()
})
export const handler: Handlers = {
GET() {
return new Response(JSON.stringify({ users: [] }))
},
POST: validateBody(userSchema)(async (req, ctx) => {
const user = ctx.state.validatedBody
return new Response(JSON.stringify({
message: 'User created successfully',
user
}), {
status: 201,
headers: { 'Content-Type': 'application/json' }
})
})
}
// 10. Route logging and analytics
// middleware/analytics.ts
export const analyticsMiddleware: MiddlewareHandler = async (req, ctx) => {
const start = Date.now()
const url = new URL(req.url)
const response = await ctx.next()
const duration = Date.now() - start
// Log request analytics
console.log({
method: req.method,
path: url.pathname,
query: Object.fromEntries(url.searchParams),
status: response.status,
duration,
timestamp: new Date().toISOString()
})
// Send to analytics service (mock)
if (process.env.ANALYTICS_ENDPOINT) {
fetch(process.env.ANALYTICS_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
path: url.pathname,
method: req.method,
status: response.status,
duration,
timestamp: new Date().toISOString()
})
}).catch(console.error)
}
return response
}
💻 Fresh Deployment typescript
🔴 complex
Deployments für Fresh-Anwendungen einschließlich Deno Deploy, Vercel und Docker
// Fresh Deployment Examples
// 1. Deno Deploy configuration
// deno.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"lock": false,
"tasks": {
"start": "deno run -A --watch=static/,routes/ main.ts"
},
"imports": {
"$fresh/": "https://deno.land/x/[email protected]/",
"preact": "https://esm.sh/[email protected]",
"preact/": "https://esm.sh/[email protected]/",
"@preact/signals": "https://esm.sh/*@preact/[email protected]",
"@preact/signals-core": "https://esm.sh/*@preact/[email protected]",
"twind": "https://esm.sh/[email protected]",
"twind/": "https://esm.sh/[email protected]/",
"$std/": "https://deno.land/[email protected]/"
}
}
// deployctl.json
{
"version": 1,
"build": {
"entrypoint": "main.ts"
},
"exclude": [
"README.md",
"deno.json",
"deno.lock"
]
}
// Environment variables for Deno Deploy
// .env
DENO_KV_ACCESS_TOKEN="your_kv_token"
DENO_KV_ENDPOINT="your_kv_endpoint"
DATABASE_URL="postgres://user:password@host:port/db"
// Database configuration for Deno Deploy
// lib/db.ts
import { connect } from 'https://deno.land/x/[email protected]/mod.ts'
export const kv = await connect({
url: Deno.env.get('DENO_KV_ENDPOINT'),
token: Deno.env.get('DENO_KV_ACCESS_TOKEN'),
})
export interface Post {
id: string
title: string
content: string
createdAt: string
}
export class PostModel {
static async create(post: Omit<Post, 'id' | 'createdAt'>): Promise<Post> {
const id = crypto.randomUUID()
const newPost: Post = {
...post,
id,
createdAt: new Date().toISOString()
}
await kv.set(['posts', id], newPost)
await kv.set(['posts_by_date', newPost.createdAt], id)
return newPost
}
static async findById(id: string): Promise<Post | null> {
return await kv.get(['posts', id])
}
static async findAll(limit = 10): Promise<Post[]> {
const posts: Post[] = []
const iter = kv.list({ prefix: ['posts'] })
for await (const entry of iter) {
posts.push(entry.value as Post)
if (posts.length >= limit) break
}
return posts.sort((a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
)
}
}
// 2. Vercel deployment
// vercel.json
{
"functions": {
"routes/index.ts": {
"runtime": "vercel-deno"
}
},
"build": {
"env": {
"DENO_VERSION": "1.40.0"
}
},
"rewrites": [
{
"source": "/(.*)",
"destination": "/routes/index.ts"
}
]
}
// api/index.ts (Vercel entry point)
import { Fresh } from '$fresh/server.ts'
import manifest from './fresh.gen.ts'
await Fresh(manifest, {
hostname: 'localhost',
port: parseInt(Deno.env.get('PORT') || '8000'),
plugins: []
})
// Deno configuration for Vercel
// import_map.json
{
"imports": {
"$fresh/": "https://deno.land/x/[email protected]/",
"preact": "https://esm.sh/[email protected]",
"preact/": "https://esm.sh/[email protected]/"
}
}
// 3. Docker deployment
// Dockerfile
FROM denoland/deno:1.40.0
# Create app directory
WORKDIR /app
# Copy dependency files
COPY deno.json deno.lock ./
# Download dependencies
RUN deno cache --unstable imports/main.ts
# Copy source code
COPY . .
# Build the application
RUN deno cache --unstable src/main.ts
# Expose port
EXPOSE 8000
# Run the application
CMD ["run", "-A", "--unstable", "src/main.ts"]
# docker-compose.yml
version: '3.8'
services:
fresh-app:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgres://user:password@db:5432/freshdb
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
volumes:
- ./static:/app/static
restart: unless-stopped
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: freshdb
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
postgres_data:
# 4. Cloudflare Pages deployment
// functions/[[path]].ts
import { Fresh } from '$fresh/server.ts'
import manifest from '../fresh.gen.ts'
await Fresh(manifest, {
hostname: 'localhost',
port: 3000
})
// wrangler.toml (if using Workers)
name = "fresh-app"
main = "./functions/[[path]].ts"
compatibility_date = "2024-01-01"
[[env.production.vars]]
ENVIRONMENT = "production"
// 5. Railway deployment
// railway.toml
[build]
builder = "nixpacks"
[deploy]
healthcheckPath = "/api/health"
healthcheckTimeout = 100
restartPolicyType = "on_failure"
restartPolicyMaxRetries = 10
[env]
PORT = "8000"
// 6. Netlify Functions deployment
// netlify/functions/[[path]].ts
import { Fresh } from '$fresh/server.ts'
import manifest from '../../fresh.gen.ts'
await Fresh(manifest, {
hostname: 'localhost',
port: 3000
})
// netlify.toml
[build]
publish = "dist"
functions = "netlify/functions"
[functions]
node_version = "18"
[[redirects]]
from = "/*"
to = "/index.html"
status = 404
// 7. GitHub Actions CI/CD
// .github/workflows/deploy.yml
name: Deploy to Deno Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Setup Deno
uses: denoland/setup-deno@v1
with:
deno-version: "1.40.0"
- name: Cache dependencies
run: deno cache --lock=deno.lock --lock-write deps.ts
- name: Run tests
run: deno task test
- name: Build application
run: deno task build
- name: Deploy to Deno Deploy
uses: denoland/deployctl@v1
with:
project: "my-fresh-app"
entrypoint: "main.ts"
root: "."
// 8. Environment-specific configurations
// main.ts
import { Fresh } from '$fresh/server.ts'
import twind from '$fresh/twind.ts'
import { State } from './types.ts'
import manifest from './fresh.gen.ts'
// Environment detection
const isDev = Deno.env.get('DENO_DEPLOYMENT_ID') === undefined
const port = Number(Deno.env.get('PORT')) || 8000
// Development-only middleware
const devPlugins = isDev ? [
// Add development plugins
] : []
// Production-only middleware
const prodPlugins = !isDev ? [
// Add production plugins
] : []
await Fresh<State>(manifest, {
port,
hostname: isDev ? 'localhost' : '0.0.0.0',
plugins: [
twind(),
...devPlugins,
...prodPlugins
]
})
// types.ts
export interface State {
// Global state types
sessionId?: string
user?: {
id: number
name: string
role: string
}
}
// 9. Health check and monitoring
// routes/api/health.ts
import { Handlers } from '$fresh/server.ts'
export const handler: Handlers = {
async GET() {
const health = {
status: 'ok',
timestamp: new Date().toISOString(),
version: Deno.env.get('APP_VERSION') || '1.0.0',
environment: Deno.env.get('DENO_ENV') || 'development',
uptime: performance.now(),
memory: {
used: Deno.memoryUsage(),
limit: null // Deno doesn't have a memory limit
}
}
// Check database connection if applicable
try {
// await checkDatabase()
health.database = 'connected'
} catch (error) {
health.database = 'error'
health.status = 'error'
}
const statusCode = health.status === 'ok' ? 200 : 503
return new Response(JSON.stringify(health), {
status: statusCode,
headers: { 'Content-Type': 'application/json' }
})
}
}
// 10. Production optimization
// main.ts (Production optimizations)
import { Fresh } from '$fresh/server.ts'
import twind from '$fresh/twind.ts'
import manifest from './fresh.gen.ts'
// Configure twind for production
const twindConfig = {
selfURL: import.meta.url,
// Production-optimized configuration
}
// Enable compression in production
const compressionMiddleware = async (req: Request, ctx: any) => {
const response = await ctx.next()
if (Deno.env.get('DENO_ENV') === 'production') {
// Add compression headers
response.headers.set('Content-Encoding', 'gzip')
}
return response
}
// Security headers for production
const securityMiddleware = async (req: Request, ctx: any) => {
const response = await ctx.next()
if (Deno.env.get('DENO_ENV') === 'production') {
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-XSS-Protection', '1; mode=block')
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
}
return response
}
await Fresh(manifest, {
plugins: [
twind(twindConfig),
securityMiddleware,
compressionMiddleware
]
})