DEV Community

How I added Gemini + GPT-4 + Claude support in one settings toggle (Next.js SaaS)

Build log from this week: I added multi-provider AI support to Melororium — users can now switch between Gemini, ChatGPT, and Claude in workspace settings, and it applies across the whole platform.

Here's the pattern I used.

The problem with hardcoding one provider
I had Gemini wired directly into every AI action. Every ai.ts function called the Gemini SDK. If a user wanted a different provider, I'd have to touch every function.

Beyond the code problem: different teams have different constraints. Some can't send data to Google's servers. Some are standardized on OpenAI internally. Forcing one provider was my product decision intruding on their infrastructure policy.

The solution: one adapter layer
I created a single getAIClient() function that reads the workspace's aiProvider setting from the DB and returns the right SDK client:

export async function getAIClient(workspaceId: string) {
const settings = await getWorkspaceSettings(workspaceId);

switch (settings.aiProvider) {
case 'openai':
return new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
case 'anthropic':
return new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
default:
return new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
}
}
Every AI action calls getAIClient() first. Prompt construction, response parsing, and error handling stay the same regardless of which client gets returned.

Why BYOK matters here
Melororium uses BYOK (bring your own key). Users paste their own API key in settings. This keeps costs transparent — they see exactly what they're spending on AI inference. It also keeps me out of the billing relationship entirely.

The adapter pattern works cleanly with BYOK because each provider's key is stored separately in workspace settings.

One thing I'd do differently
Response shapes differ between providers. OpenAI, Anthropic, and Gemini return structured responses in slightly different formats. Right now I normalize them after the call in each action. A better approach would be a thin response adapter inside getAIClient() that normalizes everything before it returns. Planning to refactor that.

Also shipped this week
Along with the provider switching:

AI analytics at two levels: all-projects overview and per-project drill-down
Subtasks inside the task drawer (kanban nested structure)
Work timer day view redesigned to show graphical time blocks per team member
Per-worker timer breakdown with task-level detail
Building Melororium as a pay-once team workspace (one-time purchase, no subscription). Writing about the build as I go.

Anything you'd do differently with the provider switching pattern? Curious whether others use a similar adapter or something more formal.

Top comments (1)

Collapse
 
alexshev profile image
Alex Shev

Provider abstraction is worth doing early if the app is serious. The tricky part is not only swapping SDK calls, it is normalizing failure modes, token limits, latency, and model-specific features without hiding the important differences from users.