---
name: mike-ai-legal-platform
description: OSS AI legal platform with Next.js frontend and Express backend for document processing and legal workflows
triggers:
- set up mike legal platform
- add mike to my project
- configure mike backend
- mike document processing
- mike supabase integration
- mike frontend setup
- deploy mike legal AI
- mike API endpoints
---
# Mike AI Legal Platform
> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection.
Mike is an open-source AI legal platform consisting of a Next.js frontend and an Express backend. It supports document processing (including DOC/DOCX to PDF via LibreOffice), Supabase Auth and Postgres, S3-compatible object storage, and multiple AI model providers.
---
## Project Structure
mike/ ├── frontend/ # Next.js application ├── backend/ # Express API, Supabase access, document processing │ └── migrations/ │ └── 000_one_shot_schema.sql # Fresh DB schema
---
## Installation
```bash
# Clone the repository
git clone https://github.com/willchen96/mike.git
cd mike
# Install dependencies for both services
npm install --prefix backend
npm install --prefix frontend
# Copy environment files
cp backend/.env.example backend/.env
cp frontend/.env.local.example frontend/.env.local
Database Setup
Run the one-shot schema in the Supabase SQL editor:
backend/migrations/000_one_shot_schema.sql
Environment Configuration
Backend (backend/.env)
# Supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=$SUPABASE_SERVICE_ROLE_KEY
# S3-compatible storage (e.g. Cloudflare R2)
S3_ENDPOINT=$S3_ENDPOINT
S3_BUCKET=$S3_BUCKET
S3_ACCESS_KEY_ID=$S3_ACCESS_KEY_ID
S3_SECRET_ACCESS_KEY=$S3_SECRET_ACCESS_KEY
S3_REGION=$S3_REGION
# AI Model Provider(s) — enable at least one
OPENAI_API_KEY=$OPENAI_API_KEY
ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY
# Server
PORT=4000
Frontend (frontend/.env.local)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY
NEXT_PUBLIC_API_URL=http://localhost:4000
Running the Platform
# Start backend (Express API on port 4000)
npm run dev --prefix backend
# Start frontend (Next.js on port 3000)
npm run dev --prefix frontend
Open http://localhost:3000.
Key Commands
# Build checks
npm run build --prefix backend
npm run build --prefix frontend
# Lint
npm run lint --prefix frontend
Required Services
| Service | Purpose |
|---|---|
| Supabase | Auth + Postgres database |
| S3-compatible storage | Document/file storage (e.g. Cloudflare R2) |
| AI model provider | At least one (OpenAI, Anthropic, etc.) |
| LibreOffice | DOC/DOCX → PDF conversion |
Installing LibreOffice (Ubuntu/Debian)
sudo apt-get install -y libreoffice
Common Patterns
Supabase Client (Backend)
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
// Fetch documents for a user
const { data, error } = await supabase
.from('documents')
.select('*')
.eq('user_id', userId);
Supabase Auth (Frontend)
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
const supabase = createClientComponentClient();
// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
email: process.env.NEXT_PUBLIC_USER_EMAIL,
password: process.env.NEXT_PUBLIC_USER_PASSWORD,
});
// Get current session
const { data: { session } } = await supabase.auth.getSession();
Uploading a Document (Frontend → Backend)
// frontend/lib/api.ts
export async function uploadDocument(file: File, token: string) {
const formData = new FormData();
formData.append('file', file);
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/documents/upload`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
body: formData,
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}
Express Route with Auth Middleware (Backend)
// backend/src/routes/documents.ts
import { Router, Request, Response } from 'express';
import { requireAuth } from '../middleware/auth';
import { supabase } from '../lib/supabase';
const router = Router();
router.get('/', requireAuth, async (req: Request, res: Response) => {
const userId = (req as any).user.id;
const { data, error } = await supabase
.from('documents')
.select('id, name, created_at, status')
.eq('user_id', userId)
.order('created_at', { ascending: false });
if (error) return res.status(500).json({ error: error.message });
return res.json(data);
});
export default router;
S3 File Upload (Backend)
// backend/src/lib/storage.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { randomUUID } from 'crypto';
const s3 = new S3Client({
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
},
});
export async function uploadToStorage(
buffer: Buffer,
mimeType: string,
originalName: string
): Promise<string> {
const key = `documents/${randomUUID()}/${originalName}`;
await s3.send(
new PutObjectCommand({
Bucket: process.env.S3_BUCKET!,
Key: key,
Body: buffer,
ContentType: mimeType,
})
);
return key;
}
DOC/DOCX to PDF Conversion (Backend)
// backend/src/lib/convert.ts
import { exec } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import fs from 'fs/promises';
const execAsync = promisify(exec);
export async function convertDocToPdf(inputPath: string): Promise<string> {
const dir = path.dirname(inputPath);
await execAsync(
`libreoffice --headless --convert-to pdf --outdir ${dir} ${inputPath}`
);
const baseName = path.basename(inputPath, path.extname(inputPath));
const outputPath = path.join(dir, `${baseName}.pdf`);
// Verify output exists
await fs.access(outputPath);
return outputPath;
}
Calling an AI Model (Backend)
// backend/src/lib/ai.ts
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export async function summarizeDocument(text: string): Promise<string> {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'system',
content: 'You are a legal assistant. Summarize the following document concisely.',
},
{ role: 'user', content: text },
],
});
return response.choices[0].message.content ?? '';
}
Next.js API Route Pattern (Frontend)
// frontend/app/api/documents/route.ts
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
export async function GET() {
const supabase = createRouteHandlerClient({ cookies });
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/documents`, {
headers: { Authorization: `Bearer ${session.access_token}` },
});
const data = await res.json();
return NextResponse.json(data);
}
Troubleshooting
LibreOffice not found
which libreoffice
# If missing:
sudo apt-get install -y libreoffice # Debian/Ubuntu
brew install --cask libreoffice # macOS
Supabase connection errors
- Verify
SUPABASE_URLincludeshttps://prefix. - Service role key must be used on the backend (never expose it in the frontend).
- Run
000_one_shot_schema.sqlvia the Supabase SQL editor before first launch.
S3 upload failures with Cloudflare R2
- Set
S3_REGIONtoautofor R2. - Ensure
S3_ENDPOINTis the full R2 endpoint:https://<account-id>.r2.cloudflarestorage.com.
Port conflicts
- Backend defaults to port
4000. Override withPORT=XXXXinbackend/.env. - Frontend defaults to port
3000. Override withnext dev -p XXXX.
Build errors
# Check TypeScript errors
npm run build --prefix backend
npm run build --prefix frontend
# Lint issues
npm run lint --prefix frontend
CORS errors between frontend and backend
Add the frontend origin to your Express CORS config:
import cors from 'cors';
app.use(cors({ origin: process.env.FRONTEND_URL || 'http://localhost:3000' }));