Switch to Convex
How to change the database provider to Convex.
Convex is a reactive database platform with real-time sync, TypeScript-first backend functions, and fully managed infrastructure. Unlike traditional databases, Convex combines the database, server functions, and real-time subscriptions into a single platform.
next-forge uses Neon as the database provider with Prisma as the ORM. This guide will provide the steps you need to switch the database provider from Neon to Convex. Since Convex replaces both the database and ORM layers, this is a more significant change than switching between SQL databases.
Here's how to switch from Neon to Convex for your next-forge project.
1. Sign up to Convex
Create a free account at convex.dev. You can manage your projects through the Convex Dashboard.
2. Replace the dependencies
Uninstall the existing dependencies...
npm uninstall @neondatabase/serverless @prisma/adapter-neon @prisma/client prisma ws @types/ws --filter @repo/database... and install Convex:
npm install convex --filter @repo/database3. Initialize Convex
From the root of your project, run:
npx convex devThis will prompt you to log in, create a new project, and generate a convex/ directory in your project root with the configuration files. It will also create a .env.local file with your CONVEX_DEPLOYMENT and NEXT_PUBLIC_CONVEX_URL variables.
4. Set up the Convex client provider
Create a client component to wrap your app with the Convex provider. Add this to your app:
'use client';
import type { ReactNode } from 'react';
import { ConvexProvider, ConvexReactClient } from 'convex/react';
import { keys } from './keys';
const convex = new ConvexReactClient(keys().NEXT_PUBLIC_CONVEX_URL);
export const ConvexClientProvider = ({ children }: { children: ReactNode }) => (
<ConvexProvider client={convex}>
{children}
</ConvexProvider>
);Then wrap your app layout with the provider:
import { ConvexClientProvider } from '@repo/database/provider';
// ...
const RootLayout = ({ children }: { children: ReactNode }) => (
<html lang="en">
<body>
<ConvexClientProvider>
{children}
</ConvexClientProvider>
</body>
</html>
);
export default RootLayout;5. Update the database package
Replace the contents of the database package's main export. Since Convex uses its own function system instead of a traditional client, the export changes significantly:
export { ConvexClientProvider } from './provider';Delete the prisma/ directory from @repo/database:
rm -rf packages/database/prismaUpdate keys.ts to use the Convex environment variable:
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';
export const keys = () =>
createEnv({
client: {
NEXT_PUBLIC_CONVEX_URL: z.url(),
},
runtimeEnv: {
NEXT_PUBLIC_CONVEX_URL: process.env.NEXT_PUBLIC_CONVEX_URL,
},
});6. Define your schema
Create a schema file in the convex/ directory. Here's an example equivalent to the default Prisma Page model:
import { defineSchema, defineTable } from 'convex/server';
import { v } from 'convex/values';
export default defineSchema({
pages: defineTable({
title: v.string(),
content: v.optional(v.string()),
}),
});Run npx convex dev to push your schema to Convex and generate types.
7. Write queries and mutations
Create server functions for your data access. Convex uses its own function system instead of raw SQL or an ORM:
import { query, mutation } from './_generated/server';
import { v } from 'convex/values';
export const list = query({
handler: async (ctx) => {
return await ctx.db.query('pages').collect();
},
});
export const create = mutation({
args: {
title: v.string(),
content: v.optional(v.string()),
},
handler: async (ctx, args) => {
return await ctx.db.insert('pages', args);
},
});8. Update your app code
Convex uses React hooks for data fetching with automatic real-time updates. Update your components to use useQuery and useMutation:
'use client';
import { useQuery, useMutation } from 'convex/react';
import { api } from '@repo/convex/_generated/api';
export const PagesList = () => {
const pages = useQuery(api.pages.list);
const createPage = useMutation(api.pages.create);
return (
<div>
<button onClick={() => createPage({ title: 'New Page' })}>
Create Page
</button>
{pages?.map((page) => (
<div key={page._id}>{page.title}</div>
))}
</div>
);
};Convex queries are reactive by default — your UI will automatically update when the underlying data changes, without any additional configuration.
For server-side data fetching (e.g. in Server Components), use the Convex HTTP client:
import { ConvexHttpClient } from 'convex/browser';
import { api } from '@repo/convex/_generated/api';
const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
const App = async () => {
const pages = await convex.query(api.pages.list);
return (
<div>
{pages.map((page) => (
<div key={page._id}>{page.title}</div>
))}
</div>
);
};
export default App;9. Replace Prisma Studio
Delete the now unused Prisma Studio app:
rm -rf apps/studioTo manage your data, use the Convex Dashboard which provides a data browser, function logs, and deployment management.
10. Deploy
When deploying your app, set the NEXT_PUBLIC_CONVEX_URL environment variable in your hosting provider (e.g. Vercel). You can find this URL in your Convex Dashboard under your project's settings.
To deploy your Convex functions to production, run:
npx convex deployThis deploys your schema and server functions to your production Convex instance.