Exemplos Fresh

Exemplos do framework web full-stack Fresh incluindo islands, routing, middleware e implantação

💻 Fresh Hello World typescript

🟢 simple

Configuração básica do framework Fresh e aplicação Hello World

// 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' }
    })
  }
}

💻 Arquitetura Islands Fresh typescript

🟡 intermediate

Componentes interativos usando o padrão Islands do Fresh

// 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>
  )
}

💻 Routing e Middleware Fresh typescript

🟡 intermediate

Padrões avançados de routing e implementação de middleware no 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
}

💻 Implantação Fresh typescript

🔴 complex

Estratégias de implantação para aplicações Fresh incluindo Deno Deploy, Vercel e 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
  ]
})