Next.js Integration
Build full-stack Next.js applications with APSO backends using both client and server components.
Setup
Install Dependencies
npm install @apso/sdkCreate 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-sideRelated
Last updated on