Skip to Content
🚀 APSO is now in public beta. Get started →

Next.js Integration

Build full-stack Next.js applications with APSO backends using both client and server components.

Setup

Install Dependencies

npm install @apso/sdk

Create Clients

// lib/apso.ts // Server-side client (for Server Components and API Routes) export const apsoServer = (token?: string) => { const { createClient } = require('@apso/sdk'); return createClient({ baseUrl: process.env.API_URL!, token, }); }; // Client-side client (for Client Components) 'use client'; import { createClient } from '@apso/sdk'; export const apso = createClient({ baseUrl: process.env.NEXT_PUBLIC_API_URL!, });

Server Components

Fetch data directly in Server Components:

// app/projects/page.tsx import { apsoServer } from '@/lib/apso'; import { cookies } from 'next/headers'; export default async function ProjectsPage() { const cookieStore = await cookies(); const token = cookieStore.get('token')?.value; const client = apsoServer(token); const projects = await client.projects.findMany({ orderBy: { createdAt: 'desc' }, }); return ( <div> <h1>Projects</h1> <ul> {projects.map(project => ( <li key={project.id}>{project.name}</li> ))} </ul> </div> ); }

With Relations

// app/projects/[id]/page.tsx import { apsoServer } from '@/lib/apso'; import { cookies } from 'next/headers'; import { notFound } from 'next/navigation'; export default async function ProjectPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params; const cookieStore = await cookies(); const token = cookieStore.get('token')?.value; const client = apsoServer(token); const project = await client.projects.findUnique({ where: { id }, include: { tasks: true }, }); if (!project) notFound(); return ( <div> <h1>{project.name}</h1> <p>{project.description}</p> <h2>Tasks ({project.tasks.length})</h2> <ul> {project.tasks.map(task => ( <li key={task.id}>{task.title}</li> ))} </ul> </div> ); }

Server Actions

Use Server Actions for mutations:

// app/projects/actions.ts 'use server'; import { apsoServer } from '@/lib/apso'; import { cookies } from 'next/headers'; import { revalidatePath } from 'next/cache'; export async function createProject(formData: FormData) { const cookieStore = await cookies(); const token = cookieStore.get('token')?.value; const client = apsoServer(token); const name = formData.get('name') as string; const description = formData.get('description') as string; await client.projects.create({ name, description: description || undefined, }); revalidatePath('/projects'); } export async function deleteProject(id: string) { const cookieStore = await cookies(); const token = cookieStore.get('token')?.value; const client = apsoServer(token); await client.projects.delete({ where: { id } }); revalidatePath('/projects'); }

Using Server Actions

// app/projects/CreateForm.tsx 'use client'; import { createProject } from './actions'; export function CreateProjectForm() { return ( <form action={createProject}> <input name="name" placeholder="Project name" required /> <textarea name="description" placeholder="Description" /> <button type="submit">Create</button> </form> ); }

API Routes

Create API routes that proxy to your APSO backend:

// app/api/projects/route.ts import { apsoServer } from '@/lib/apso'; import { cookies } from 'next/headers'; import { NextRequest, NextResponse } from 'next/server'; export async function GET() { const cookieStore = await cookies(); const token = cookieStore.get('token')?.value; if (!token) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const client = apsoServer(token); const projects = await client.projects.findMany(); return NextResponse.json(projects); } export async function POST(request: NextRequest) { const cookieStore = await cookies(); const token = cookieStore.get('token')?.value; if (!token) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const body = await request.json(); const client = apsoServer(token); const project = await client.projects.create(body); return NextResponse.json(project, { status: 201 }); }

Authentication

Login API Route

// app/api/auth/login/route.ts import { apsoServer } from '@/lib/apso'; import { cookies } from 'next/headers'; import { NextRequest, NextResponse } from 'next/server'; export async function POST(request: NextRequest) { const { email, password } = await request.json(); const client = apsoServer(); try { const { token, user } = await client.auth.login({ email, password }); const cookieStore = await cookies(); cookieStore.set('token', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 60 * 60 * 24 * 7, // 7 days }); return NextResponse.json({ user }); } catch (error) { return NextResponse.json( { error: 'Invalid credentials' }, { status: 401 } ); } }

Middleware Protection

// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const token = request.cookies.get('token')?.value; const isAuthPage = request.nextUrl.pathname.startsWith('/login'); const isProtectedPage = request.nextUrl.pathname.startsWith('/dashboard'); if (isProtectedPage && !token) { return NextResponse.redirect(new URL('/login', request.url)); } if (isAuthPage && token) { return NextResponse.redirect(new URL('/dashboard', request.url)); } return NextResponse.next(); } export const config = { matcher: ['/dashboard/:path*', '/login'], };

Client Components

For interactive features, use Client Components:

// components/ProjectSearch.tsx 'use client'; import { useState, useEffect } from 'react'; import { apso } from '@/lib/apso'; export function ProjectSearch() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); useEffect(() => { if (query.length < 2) { setResults([]); return; } const timer = setTimeout(async () => { const projects = await apso.projects.findMany({ where: { name: { contains: query } }, take: 5, }); setResults(projects); }, 300); return () => clearTimeout(timer); }, [query]); return ( <div> <input value={query} onChange={e => setQuery(e.target.value)} placeholder="Search projects..." /> <ul> {results.map(project => ( <li key={project.id}>{project.name}</li> ))} </ul> </div> ); }

Environment Variables

# .env.local API_URL=http://localhost:3001 # Server-side only NEXT_PUBLIC_API_URL=http://localhost:3001 # Client-side
Last updated on