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
- Always use the singleton instance:
authnotnew ApiClient() - Handle ApiError specifically: Check for validation errors
- Use TypeScript interfaces: Import
User,RegisterData, etc. - Clear errors on retry: Reset error states before new requests
- Show loading states: Use
isLoadingfor better UX