NEXORA: Building a Multi-Role SaaS Platform with OpenAI and 80%+ Test Coverage
2026-06-12 · 5 min read
What NEXORA Is
NEXORA is a multi-utility SaaS platform that demonstrates what I consider the baseline quality bar for any production web application:
- Authentication: JWT with refresh tokens, not just access tokens
- Authorization: Role-based access control, not "is admin" booleans
- AI Integration: OpenAI API with proper error handling, not raw fetch calls
- Observability: Real-time usage analytics for administrators
- Testing: 80%+ unit test coverage with mocked external dependencies
- CI/CD: Automated pipeline from push to deployment
Authentication: JWT Done Right
A common mistake is issuing only access tokens. When they expire (typically after 15 minutes), the user is logged out. The fix: separate access tokens (short-lived) and refresh tokens (long-lived, stored in httpOnly cookies):
// src/auth/tokens.ts
export function issueTokenPair(userId: string, role: UserRole) {
const accessToken = jwt.sign(
{ userId, role },
process.env.ACCESS_TOKEN_SECRET!,
{ expiresIn: "15m" },
);
const refreshToken = jwt.sign(
{ userId },
process.env.REFRESH_TOKEN_SECRET!,
{ expiresIn: "30d" },
);
return { accessToken, refreshToken };
}
// Refresh endpoint
export async function POST(req: Request) {
const refreshToken = req.cookies.get("refresh_token")?.value;
if (!refreshToken) return unauthorized();
const payload = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET!);
const user = await db.user.findUnique({ where: { id: payload.userId } });
if (!user || user.refreshToken !== refreshToken) return unauthorized();
const { accessToken, refreshToken: newRefreshToken } = issueTokenPair(user.id, user.role);
// Rotate refresh token on each use (prevents refresh token theft)
await db.user.update({ where: { id: user.id }, data: { refreshToken: newRefreshToken } });
const response = NextResponse.json({ accessToken });
response.cookies.set("refresh_token", newRefreshToken, {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 30 * 24 * 60 * 60,
});
return response;
}
Refresh token rotation is critical: each use of a refresh token issues a new one. If a stolen token is used, the legitimate user's next request will fail (their token was already rotated) — alerting the system.
RBAC Implementation
// src/middleware/rbac.ts
const ROUTE_PERMISSIONS: Record<string, UserRole[]> = {
"/api/admin/*": ["administrator"],
"/api/analytics/*": ["administrator", "manager"],
"/api/content/generate": ["user", "manager", "administrator"],
"/api/users/*": ["administrator"],
};
export function withRBAC(handler: NextApiHandler, allowedRoles: UserRole[]) {
return async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getSession(req);
if (!session || !allowedRoles.includes(session.role)) {
return res.status(403).json({ error: "Forbidden" });
}
return handler(req, res);
};
}
OpenAI Integration with Proper Error Handling
Raw OpenAI API calls fail in production. Rate limits, timeouts, and content policy violations are all real failure modes that need explicit handling:
// src/services/ai-generation.ts
export async function generateContent(
prompt: string,
userId: string,
): Promise<GenerationResult> {
// Rate limit check before calling API
const usage = await getHourlyUsage(userId);
if (usage >= 20) {
throw new RateLimitError("You've reached the hourly generation limit.");
}
try {
const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: prompt }],
max_tokens: 1000,
});
const content = response.choices[0].message.content!;
// Log usage for analytics
await db.usageEvent.create({
data: {
userId,
type: "content_generation",
tokensUsed: response.usage?.total_tokens,
timestamp: new Date(),
},
});
return { content, tokensUsed: response.usage?.total_tokens };
} catch (error) {
if (error instanceof OpenAI.APIError) {
if (error.status === 429) throw new RateLimitError("OpenAI rate limit hit.");
if (error.status === 400) throw new ContentPolicyError("Content policy violation.");
}
throw error;
}
}
Testing with Mocked External Services
The key to 80%+ coverage on AI-integrated code: mock the OpenAI client at the service layer, not the HTTP layer.
// src/services/__tests__/ai-generation.test.ts
jest.mock("openai", () => ({
default: jest.fn().mockImplementation(() => ({
chat: {
completions: {
create: jest.fn(),
},
},
})),
}));
describe("generateContent", () => {
let mockCreate: jest.Mock;
beforeEach(() => {
const OpenAI = require("openai").default;
mockCreate = new OpenAI().chat.completions.create;
});
it("returns generated content on success", async () => {
mockCreate.mockResolvedValueOnce({
choices: [{ message: { content: "Generated text here" } }],
usage: { total_tokens: 150 },
});
const result = await generateContent("Test prompt", "user-123");
expect(result.content).toBe("Generated text here");
expect(result.tokensUsed).toBe(150);
});
it("throws RateLimitError when user limit exceeded", async () => {
await setHourlyUsage("user-123", 20); // mock at limit
await expect(generateContent("Test prompt", "user-123"))
.rejects.toThrow(RateLimitError);
expect(mockCreate).not.toHaveBeenCalled();
});
it("throws RateLimitError on 429 from OpenAI", async () => {
mockCreate.mockRejectedValueOnce(new OpenAI.APIError(429, {}, "Rate limited", {}));
await expect(generateContent("Test prompt", "user-123"))
.rejects.toThrow(RateLimitError);
});
});
CI/CD Pipeline
# .github/workflows/ci.yml
name: CI/CD Pipeline
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20" }
- run: npm ci
- run: npm run lint
- run: npm run test -- --coverage --coverageThreshold='{"global":{"lines":80}}'
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t nexora:${{ github.sha }} .
- name: Push to registry
run: docker push ${{ env.REGISTRY }}/nexora:${{ github.sha }}
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: ssh ${{ secrets.DEPLOY_HOST }} "docker pull ... && docker compose up -d"
The coverageThreshold flag in Jest fails the CI pipeline if coverage drops below 80%. This is the enforcement mechanism — without it, coverage goals are aspirational rather than enforced.
GitHub: github.com/karthikrshet/Nexora