Zum Hauptinhalt springen

API Client Library

The frontend uses a TypeScript API client library for seamless communication with the Laravel backend API.

Overview

File: apps/web/lib/api.ts

The API client provides:

  • ✅ Type-safe API calls
  • ✅ Automatic token management
  • ✅ Error handling
  • ✅ camelCase/snake_case conversion
  • ✅ Authentication state management

Configuration

// Environment Variables
NEXT_PUBLIC_API_URL=https://api.hoffnungstraeger-sprachbruecke.de/api

Core API Client

Initialization

import { auth } from '@/lib/api'

// Singleton instance - automatically handles tokens
const apiClient = auth

Authentication Methods

// Registration
const response = await auth.register({
firstName: "Maria",
lastName: "Schmidt",
email: "maria@example.com",
password: "password123",
passwordConfirmation: "password123",
role: "requester",
organizationName: "Hospital Berlin",
organizationType: "healthcare"
})

// Login
const response = await auth.login({
email: "maria@example.com",
password: "password123",
role: "requester"
})

// Get Profile
const user = await auth.getProfile()

// Logout
await auth.logout()

// Check Authentication
const isAuthenticated = auth.isAuthenticated()
const token = auth.getToken()

Data Types

User Interface

interface User {
id: string
email: string
firstName: string
lastName: string
phone?: string
roles: Array<{
id: number
name: string
guardName: string
createdAt: string
updatedAt: string
pivot?: any
}>
state?: string
emailVerifiedAt?: string | null
createdAt: string
updatedAt: string

// Profile data (nested from Laravel)
clientProfile?: {
id: string
userId: string
organizationName: string
organizationType: string
billingEmail?: string | null
billingReference?: string | null
notes?: string | null
createdAt: string
updatedAt: string
} | null

interpreterProfile?: {
id: string
userId: string
languages?: string[]
travelRadius?: number
experience?: string
qualifications?: string
hourlyRate?: number
createdAt: string
updatedAt: string
} | null

permissions?: string[]
}

Registration Data

interface RegisterData {
// Basic fields
firstName: string
lastName: string
email: string
password: string
passwordConfirmation: string
role: 'interpreter' | 'requester'

// Optional personal data
salutation?: string
phone?: string
phoneCountryCode?: string

// Interpreter specific
languages?: string[]
travelRadius?: number

// Requester specific
organizationName?: string
organizationType?: string
position?: string
department?: string

// Address data
street?: string
houseNumber?: string
city?: string
postalCode?: string
country?: string

// Terms acceptance
acceptTerms: boolean
acceptPrivacy: boolean
}

Login Data

interface LoginData {
email: string
password: string
role: 'interpreter' | 'requester'
remember?: boolean
}

Token Management

Automatic Token Handling

// Tokens are automatically:
// ✅ Stored in localStorage
// ✅ Added to request headers
// ✅ Refreshed when needed
// ✅ Cleared on logout

class ApiClient {
private token: string | null = null

constructor() {
// Auto-load token from localStorage
this.token = localStorage.getItem('auth_token')
}

setToken(token: string) {
this.token = token
localStorage.setItem('auth_token', token)
}

clearToken() {
this.token = null
localStorage.removeItem('auth_token')
}
}

Request Interceptor

private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'Accept': 'application/json',
...(options.headers as Record<string, string>),
}

// Automatic token injection
if (this.token) {
headers['Authorization'] = `Bearer ${this.token}`
}

const response = await fetch(url, { ...options, headers })

// Handle API errors
if (!response.ok) {
const error = await response.json()
throw new ApiError(error.message, error.errors)
}

return response.json()
}

Error Handling

ApiError Class

export class ApiError extends Error {
public errors: Record<string, string[]> | undefined

constructor(message: string, errors?: Record<string, string[]>) {
super(message)
this.errors = errors
}
}

Usage in Components

try {
const response = await auth.login(loginData)
// Success - redirect to dashboard
window.location.href = '/dashboard'
} catch (error) {
if (error instanceof ApiError) {
// API validation errors
if (error.errors?.email) {
setEmailError(error.errors.email[0])
}
setApiError(error.message)
} else {
// Network or unexpected errors
setApiError('Ein unerwarteter Fehler ist aufgetreten')
}
}

Integration Examples

Registration Form

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setApiError("")
setFieldErrors({})

try {
const registrationData: RegisterData = {
firstName,
lastName,
email,
password,
passwordConfirmation,
role: activeTab as 'interpreter' | 'requester',
// ... other fields
}

const response = await auth.register(registrationData)

// Auto-login after successful registration
router.push('/dashboard')
} catch (error) {
if (error instanceof ApiError) {
setApiError(error.message)
setFieldErrors(error.errors || {})
}
} finally {
setIsLoading(false)
}
}

Protected Routes

const checkAuth = async () => {
try {
if (!auth.isAuthenticated()) {
router.push('/login')
return
}

const profile = await auth.getProfile()
setUser(profile)
} catch (error) {
router.push('/login')
}
}

useEffect(() => {
checkAuth()
}, [])

Configuration

Environment Setup

# .env.local
NEXT_PUBLIC_API_URL=http://localhost:8000/api # Development
# NEXT_PUBLIC_API_URL=https://api.sprachbruecke.de/api # Production

TypeScript Configuration

The API client is fully typed with TypeScript for:

  • 🔒 Type Safety: Compile-time error checking
  • 🚀 IntelliSense: Auto-completion in IDEs
  • 📖 Documentation: Self-documenting interfaces
  • 🐛 Debugging: Better error messages

Best Practices

  1. Always use the singleton instance: auth not new ApiClient()
  2. Handle ApiError specifically: Check for validation errors
  3. Use TypeScript interfaces: Import User, RegisterData, etc.
  4. Clear errors on retry: Reset error states before new requests
  5. Show loading states: Use isLoading for better UX