API-utvikling
Denne guiden dekker API-utvikling med Fastify, autentisering, tier-haandheving, AI-workloads og jobbkoer.
Rutemonster
heading.anchorLabelAPI-et bruker Fastify med OpenAPI-skjemavalidering og middleware-kjeding.
Mappestruktur
heading.anchorLabelapps/api/src/├── routes/ # Alle API-endepunkter│ ├── impulses/ # CRUD + AI-analyse│ ├── sessions/ # 5-stegs sesjoner│ ├── check-ins/ # Sjekk-inn-vurdering│ ├── insights/ # AI-innsikt + analytics│ ├── dashboard/ # Home + journey dashboard│ ├── profiles/ # Brukerprofiladministrasjon│ ├── subscriptions/ # Abonnementsynkronisering│ ├── webhooks/ # App Store + Google Play│ ├── ai/ # AI rate limit + status│ ├── quota/ # Kvoteinformasjon│ └── feedback/ # Brukertilbakemelding├── middleware/ # Auth, subscription, rate limiter├── ai/ # AI-workloads├── queues/ # BullMQ-jobbkoer├── services/ # Forretningslogikk├── state-machines/ # Robot3-tilstandsmaskiner└── lib/ # Supabase, Redis, SentryStandardmonster for ruter
heading.anchorLabelimport { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'import { authenticateJWT } from '../../middleware/auth.js'import { requireSubscription } from '../../middleware/subscription.js'
export async function myRoutes(fastify: FastifyInstance) { // Autentisering pa alle ruter i denne gruppen fastify.addHook('preHandler', authenticateJWT)
fastify.post<{ Body: CreateItemBody }>( '/', { schema: { description: 'Opprett et nytt element', tags: ['items'], body: { type: 'object', required: ['content'], properties: { content: { type: 'string', minLength: 1 }, }, }, response: { 201: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', additionalProperties: true }, }, }, }, }, preHandler: [requireSubscription], }, async (request, reply) => { // 1. Ekstraher og valider input const { content } = request.body const userId = request.user.id
// 2. Utfor forretningslogikk const item = await createItem(userId, content)
// 3. Returner strukturert respons return reply.code(201).send({ success: true, data: { item }, }) } )}Autentisering
heading.anchorLabelJWT-middleware
heading.anchorLabelauthenticateJWT verifiserer JWT-tokens fra Supabase Auth og legger brukerinfo pa request.user.
// Bruk i ruter:fastify.addHook('preHandler', authenticateJWT)
// Tilgang til bruker i handler:const userId = request.user.idDev API token (bypass)
heading.anchorLabelFor raskere testing uten ekte autentisering:
# Generer tokenopenssl rand -hex 32
# Legg til i apps/api/.env.local# DEV_API_TOKEN=din-token
# Bruk i kallcurl -H "X-Dev-Token: din-token" http://localhost:3001/api/v1/impulses
# Velg bruker-tiercurl -H "X-Dev-Token: din-token" \ -H "X-Dev-User-Tier: trial" \ http://localhost:3001/api/v1/impulsesAbonnement og tier-haandheving
heading.anchorLabelAbonnementsmodell
heading.anchorLabel| Tier | Pris | AI-tilgang | Kvoter |
|---|---|---|---|
trial | Gratis (3 dager) | Full tilgang | 3 impulser + 7 sjekk-inn |
foundation | $6/mnd | Full tilgang | 30 sesjoner/mnd |
mastery | $12/mnd | Full tilgang | Ubegrenset |
partner | Gratis | Full tilgang | 30 sesjoner/mnd |
Subscription-middleware
heading.anchorLabelimport { requireSubscription } from '../../middleware/subscription.js'
// Krev aktiv abonnementfastify.get('/protected', { preHandler: [authenticateJWT, requireSubscription],}, handler)Middleware-en:
- Henter brukerens abonnementsprofil (Redis-cachet, 1 time TTL)
- Sjekker at status er
active - Returnerer 403 hvis abonnement mangler eller er utlopt
Statustyper
heading.anchorLabel| Status | Betydning |
|---|---|
active | Full tilgang |
expired | Trial utlopt, trenger abonnement |
inactive | Abonnement kansellert |
grace_period | Betalingsproblemer, midlertidig tilgang |
billing_retry | Faktureringsforsoket feiler |
JSONB-serialiseringskonvensjon
heading.anchorLabelAPI-et folger en todelt konvensjon for serialisering:
| Lag | Konvensjon | Stabilitet |
|---|---|---|
| DB-kolonner | snake_case | Stabil |
| JSONB-innhold | camelCase | Dynamisk |
JSONB-innhold fra AI-workloads endres ofte. Definer aldri JSONB-innhold i Fastify-skjemaer:
// FEIL -- vil brekke nar AI endrer outputai_analysis: { type: ['object', 'null'], properties: { sessionCTA: { type: 'string' } }}
// RIKTIG -- behandle som opakai_analysis: { type: ['object', 'null'], additionalProperties: true}AI-workload-utvikling
heading.anchorLabelArkitektur
heading.anchorLabelai/├── infrastructure/│ ├── anthropic/ # Provider + modellkonfig│ ├── access/ # AI-tilgangskontroll│ ├── tracking/ # Brukssporing│ ├── safety/ # Sikkerhetssjekker│ └── voyage/ # Embedding-klient├── impulses/│ └── classification/ # Klassifiser impulser├── sessions/│ ├── step-guidance.ts # Steg-veiledning│ └── session-summary.ts # Sesjonsoppsummering├── check-ins/│ └── assessment/ # Sjekk-inn-vurdering├── insights/│ ├── narrative.ts # Innsiktsnarrativ│ ├── pulse.ts # Kort puls-innsikt│ └── embedding-analytics.ts└── shared/ ├── prompt-builder.ts # Prompt-konstruksjon ├── conventions.ts # Felles konvensjoner └── types.ts # Delte typerModellniva
heading.anchorLabelAlle workloads bruker Sonnet 4.6 som standard. Tre tier-er bestemmer modellvalg:
| Tier | Modell | Workloads |
|---|---|---|
fast | Sonnet (Haiku-safe) | classify_analytical |
balanced | Sonnet (ma vaere Sonnet) | classify_enrichment, guidance, pulse, check_in_assess |
quality | Sonnet+ | summary, insight |
Override via miljovaribler: AI_MODEL_FAST, AI_MODEL_BALANCED, AI_MODEL_QUALITY.
Jobbkoer (BullMQ)
heading.anchorLabelAlle asynkrone AI-operasjoner bruker BullMQ med Redis.
Monster
heading.anchorLabel1. POST-endepunkt oppretter ressurs + legger jobb i ko -> returnerer umiddelbart2. Klient kobler til SSE-endepunkt for statusoppdateringer3. Ko prosesserer jobb og sender oppdateringer4. Klient mottar ferdig/feil via SSE| Ko | Prioritet | Beskrivelse |
|---|---|---|
impulseClassification | 1 (realtime) | Klassifiser impulser |
impulseAnalysis | 1 (realtime) | Analyseanrikning |
unifiedGuidance | 1 (realtime) | Steg-veiledning i sesjoner |
checkInAssessment | 1 (realtime) | Sjekk-inn-vurdering |
sessionSummary | 2 (near-realtime) | Sesjonsoppsummering |
insightsRegeneration | 5 (bakgrunn) | Regenerer innsikter |
Prioritetsniva
heading.anchorLabel| Prioritet | Type | Kontekst |
|---|---|---|
| 1 | Realtime | Bruker venter aktivt |
| 2 | Near-realtime | Bruker venter, men mindre akutt |
| 5 | Bakgrunn | Ingen bruker venter |
| 10 | Batch | Kan ta timer |
Tilstandsmaskiner (Robot3)
heading.anchorLabelAPI-et bruker Robot3 for a haandtere statusoverganger.
apps/api/src/state-machines/├── blockage-machine.ts # new -> clearing -> cleared├── intention-machine.ts # new -> realizing -> realized├── clearing-process-machine.ts # 5-stegs clearing├── realization-process-machine.ts # 5-stegs realisering└── integration-machine.ts # 5-stegs integrasjonHvert maskin-modul eksporterer:
createMachine() // Maskin-definisjoncanTransition(currentStatus, event): boolean // Kan vi ga til denne tilstanden?getNextStatus(currentStatus, event): Status // Hva er neste tilstand?Testing med dev-token
heading.anchorLabel# Opprett impulscurl -X POST http://localhost:3001/api/v1/impulses \ -H "X-Dev-Token: din-token" \ -H "Content-Type: application/json" \ -d '{"content": "Jeg kjenner motstand mot a ta kontakt"}'
# List impulsercurl http://localhost:3001/api/v1/impulses \ -H "X-Dev-Token: din-token"
# Hent dashboardcurl http://localhost:3001/api/v1/dashboard/home \ -H "X-Dev-Token: din-token"
# Sjekk kvotercurl http://localhost:3001/api/v1/quota \ -H "X-Dev-Token: din-token"Nøkkelfiler
heading.anchorLabel| Beskrivelse | Sti |
|---|---|
| API-server | apps/api/src/server.ts |
| Auth-middleware | apps/api/src/middleware/auth.ts |
| Subscription-middleware | apps/api/src/middleware/subscription.ts |
| Kvote-middleware | apps/api/src/middleware/quota.ts |
| AI-konfig | apps/api/src/ai/infrastructure/anthropic/config.ts |
| Ko-infrastruktur | apps/api/src/queues/index.ts |
| Tilstandsmaskiner | apps/api/src/state-machines/ |
| Tier-enforcement | apps/api/src/helpers/tier-enforcement.ts |