Introduction
The right to data portability, defined in Article 20 of the GDPR, allows users to retrieve their data in a structured, machine-readable format. In 2026, companies must offer automated, interoperable, and secure exports. This tutorial guides you through building a complete solution with Next.js and TypeScript, including compliant JSON file generation and a protected API endpoint.
Prerequisites
- Node.js 20+
- Next.js 15 with TypeScript
- PostgreSQL database and Prisma
- Basic GDPR knowledge
- AWS S3 account or equivalent for temporary storage
Prisma Configuration
model User {
id String @id @default(uuid())
email String @unique
data Json
createdAt DateTime @default(now())
}
model DataExport {
id String @id @default(uuid())
userId String
fileUrl String
status String @default("pending")
createdAt DateTime @default(now())
}This schema defines the User and DataExport models. The data field stores the user's portable information while DataExport tracks export requests.
Data Extraction Function
import { prisma } from './prisma';
export async function exportUserData(userId: string) {
const user = await prisma.user.findUnique({
where: { id: userId },
include: { orders: true, preferences: true }
});
if (!user) throw new Error('Utilisateur introuvable');
return {
user: { email: user.email, createdAt: user.createdAt },
orders: user.orders,
preferences: user.preferences,
exportedAt: new Date().toISOString()
};
}This function retrieves all portable user data via Prisma. It structures the result according to GDPR requirements by including a timestamp.
JSON File Generation
import { exportUserData } from './exportData';
import fs from 'fs/promises';
import path from 'path';
export async function generateExportFile(userId: string) {
const data = await exportUserData(userId);
const filename = `export-${userId}-${Date.now()}.json`;
const filepath = path.join('/tmp', filename);
await fs.writeFile(filepath, JSON.stringify(data, null, 2));
return { filepath, filename };
}The JSON file is generated in a temporary folder with a unique name. The format is machine-readable and follows the structure defined by the GDPR.
Export Request API Endpoint
import { NextRequest, NextResponse } from 'next/server';
import { generateExportFile } from '@/lib/generateExport';
export async function POST(req: NextRequest) {
const { userId } = await req.json();
const { filepath, filename } = await generateExportFile(userId);
// Upload vers S3 puis suppression locale
return NextResponse.json({
message: 'Export généré',
downloadUrl: `/api/download/${filename}`
});
}This POST endpoint triggers export generation. It returns a temporary download URL and must be protected by authentication.
Secure Download Endpoint
import { NextRequest, NextResponse } from 'next/server';
import fs from 'fs/promises';
import path from 'path';
export async function GET(req: NextRequest, { params }: { params: { filename: string } }) {
const filePath = path.join('/tmp', params.filename);
const file = await fs.readFile(filePath);
return new NextResponse(file, {
headers: {
'Content-Type': 'application/json',
'Content-Disposition': `attachment; filename=${params.filename}`
}
});
}This endpoint enables direct JSON file download. It implicitly verifies rights through the unique filename and temporary link.
Best Practices
- Limit download link lifetime to a maximum of 24 hours
- Encrypt exports at rest with AES-256
- Log every request with timestamp and IP
- Also offer CSV format for better interoperability
- Notify the user by email as soon as the export is available
Common Mistakes to Avoid
- Exposing sensitive data without prior anonymization
- Forgetting to delete temporary files after 48 hours
- Failing to validate user identity before export
- Returning incomplete exports without including GDPR metadata
Further Reading
Discover our complete training on GDPR compliance and secure development: https://learni-group.com/formations. Also explore the article on encrypting personal data to strengthen your implementation.