This guide explores Convex's self-hosting capability and demonstrates how to use it with Neon Postgres. Convex is a reactive backend platform ideal for building real-time applications. A recent release significantly enhances the self-hosted experience, overcoming limitations of the initial open-source version which lacked a dashboard and relied solely on SQLite. The new self-hosted Convex includes the dashboard and supports Postgres as a robust and scalable database option.
Convex empowers developers to create dynamic, live-updating applications. Self-hosting retains these core features while granting you greater control over your deployment environment. While SQLite remains the default for simplicity, Postgres integration unlocks enhanced scalability and resilience, especially beneficial for production applications.
This guide provides a step-by-step walkthrough of integrating Convex with Neon Postgres. You will learn how to:
- Set up Convex for self-hosting using Docker Compose.
- Configure Convex to utilize Neon Postgres for persistent data storage.
- Run the Convex chat application tutorial as a practical example.
- Test the integration to ensure everything functions correctly.
Prerequisites
Before you begin, ensure you have the following prerequisites installed and configured:
- Neon Account: Sign up for a free Neon account if you don't have one already. Neon will provide a managed Postgres database for your Convex chat application.
- Docker: Docker is essential for running the Convex backend and dashboard locally. If Docker is not installed, download and install Docker Desktop from docker.com. Make sure Docker is running before proceeding.
- Node.js v18+: Node.js (version 18 or higher) is required to run the Convex chat application example. Download and install it from nodejs.org.
Database Location and Latency Considerations
Remember that the physical distance between your Neon database and your self-hosted Convex backend can impact your application's performance due to latency. Increased distance generally means higher latency and potentially slower response times.
For optimal performance, especially in production, it's highly recommended to locate your Neon database and Convex backend in the same geographical region. Convex's cloud-hosted platform achieves extremely low query times because the database and backend are co-located within their infrastructure.
While this guide focuses on setup and integration specifically for local development, for production applications, consider the physical proximity of your Neon Postgres and Convex Backend server to minimize latency.
Setting up Neon Database
To get started with your Postgres database, create a new Neon project using pg.new. This project will provide the Postgres instance that Convex will use to store your application data. Within this Neon project, you'll need to create a database named convex_self_hosted
– this is the specific database Convex is configured to use for storing chat messages. Follow these steps to set up your Neon Postgres database:
-
Navigate to the SQL Editor in your Neon project console to create the
convex_self_hosted
database. -
Execute the following SQL command to create the database:
CREATE DATABASE convex_self_hosted;
-
Once the database is created, you can retrieve the connection string by clicking on "Connect" in the Neon project's dashboard. Select the
convex_self_hosted
database and copy the connection string. You will need this connection string later to configure the Convex backend to use Neon Postgres.
Setting up Self-Hosted Convex with Docker Compose
Now, you'll set up the self-hosted Convex backend using Docker Compose, configuring it to use your Neon Postgres database.
-
Create a Project Directory: Open your terminal and create a new directory for your Convex project. Navigate into it:
mkdir convex-neon-integration cd convex-neon-integration
-
Download Docker Compose Configuration: Download the default
docker-compose.yml
file provided by Convex directly into your project directory:npx degit get-convex/convex-backend/self-hosted/docker/docker-compose.yml docker-compose.yml
This command uses
npx degit
to fetch thedocker-compose.yml
file from the Convex GitHub repository. -
Set up Neon connection string: Add your Neon connection string you copied earlier to a
.env
file to configure Convex.-
Create a
.env
file in the same directory asdocker-compose.yml
. -
Add this line:
POSTGRES_URL=[YOUR_NEON_CONNECTION_STRING]
-
Modify
[YOUR_NEON_CONNECTION_STRING]
for Convex:Convex requires a specific connection string format for Neon:
postgres://username:password@hostname
Remove the database name and extra parameters from your Neon connection string.
Neon default:
postgresql://neondb_owner:password@ep-xxxxx.aws.neon.tech/convex_self_hosted?sslmode=require
For Convex:
postgres://neondb_owner:password@ep-xxxxx.aws.neon.tech
-
-
Start Convex services with Docker Compose: With the configuration in place, start the Convex backend and dashboard services using Docker Compose. Execute the following command in your terminal within the
convex-neon-integration
directory:docker compose up -d
The
-d
flag runs the containers in detached mode (in the background). Docker Compose will download the necessary images, create containers, and start the Convex services. -
Access the Convex Dashboard: Once
docker compose up -d
completes, the Convex dashboard should be accessible in your browser at http://localhost:6791. It might take a few moments for the services to fully start. If it's not immediately available, wait a short time and refresh the page.You should see the Convex dashboard login screen:
Login to the Convex Dashboard:
- When you access the dashboard for the first time, you will be prompted to log in.
- For the password, you will use the
CONVEX_SELF_HOSTED_ADMIN_KEY
generated in the next step.
-
Verify Neon Postgres Connection (Optional but Recommended): You can confirm that Convex is using your Neon Postgres database by checking the Docker container logs. This verifies that the
POSTGRES_URL
environment variable was correctly processed.Run this command in your terminal within the
convex-neon-integration
directory:docker compose logs -f
Examine the logs for messages indicating a successful connection to a Postgres database, similar to the example below:
-
Retrieve the Admin Key: You need the
CONVEX_SELF_HOSTED_ADMIN_KEY
to log into the Convex dashboard and configure your chat application. Execute this command to retrieve it:docker compose exec backend ./generate_admin_key.sh
The output will display the generated admin key. Copy this key carefully. You'll need it in the next steps to log in to the dashboard and configure the chat application.
convex-self-hosted|01xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Use this admin key as the password when logging into the Convex dashboard at http://localhost:6791
Setting up the Convex chat application example
With the self-hosted Convex backend powered by Neon running, the next step is to set up the Convex chat application example to connect to this backend and complete the chat functionality as described in the Convex tutorial.
-
Clone the Convex tutorial repository: Open a new terminal window, ensuring the Docker Compose process from the previous step remains running. Clone the Convex tutorial repository to your local machine. This repository contains the source code for the chat application example.
git clone https://github.com/get-convex/convex-tutorial.git cd convex-tutorial
-
Install application dependencies: Navigate into the
convex-tutorial
directory and install the required npm packages. This step downloads all necessary JavaScript libraries and dependencies for the chat application.npm install
-
Update Convex library version: It is required to update the
convex
npm package to the latest version within theconvex-tutorial
project. This is needed as the existing version present in the tutorial repository is not the latest and will cause issues with the self-hosted Convex backend.npm install convex@latest
-
Configure environment variables for chat app: To connect the chat application to your self-hosted Convex backend, you need to configure specific environment variables. Create a
.env.local
file in the root of theconvex-tutorial
directory. Add the following variables to this file:VITE_CONVEX_URL=http://localhost:3210 CONVEX_SELF_HOSTED_URL='http://localhost:3210' CONVEX_SELF_HOSTED_ADMIN_KEY='<your_generated_admin_key>'
VITE_CONVEX_URL
: Specifies the URL of your self-hosted Convex backend. In this case, it's set tohttp://localhost:3210
, the default for local Convex backends.CONVEX_SELF_HOSTED_URL
: Also set to the same URL,http://localhost:3210
.CONVEX_SELF_HOSTED_ADMIN_KEY
: This key is essential for authenticating development operations against your self-hosted Convex instance. Replace<your_generated_admin_key>
with the admin key you generated in the previous step Setting up self-hosted Convex with Docker Compose.
-
Initialize Convex project: Run the following command to initialize the Convex project and generate the necessary TypeScript files for the chat application:
npm run predev
This starts the Convex server and generates the necessary TypeScript files for the chat application.
-
Implement the
sendMessage
Mutation: Following the Convex tutorial - Your first mutation section, create a new fileconvex/chat.ts
in yourconvex-tutorial
project. Add the following code to this file. This code defines a Convex mutation function to insert new messages into the database:// convex/chat.ts import { mutation } from './_generated/server'; import { v } from 'convex/values'; export const sendMessage = mutation({ args: { user: v.string(), body: v.string(), }, handler: async (ctx, args) => { console.log('This TypeScript function is running on the server.'); await ctx.db.insert('messages', { user: args.user, body: args.body, }); }, });
-
Update
src/App.tsx
to usesendMessage
mutation: Now, update thesrc/App.tsx
file. Modify thesrc/App.tsx
file to include theuseMutation
hook and call thesendMessage
mutation when a user submits a message. Replace the relevant section insrc/App.tsx
with the following code:// src/App.tsx import { useMutation } from 'convex/react'; import { api } from '../convex/_generated/api'; // ... other imports and component setup export default function App() { const sendMessage = useMutation(api.chat.sendMessage); // ... other hooks and state variables return ( <main className="chat"> {/* ... other JSX elements */} <form onSubmit={async (e) => { e.preventDefault(); alert('Mutation not implemented yet'); await sendMessage({ user: NAME, body: newMessageText }); setNewMessageText(''); }} > {/* ... form input and button */} </form> </main> ); }
-
Implement the
getMessages
query: Following the Convex tutorial - Your first query section, add a Convex query function toconvex/chat.ts
to fetch messages from the database. Add the followinggetMessages
query function to yourconvex/chat.ts
file:// convex/chat.ts import { query, mutation } from './_generated/server'; // ... existing sendMessage mutation export const getMessages = query({ args: {}, handler: async (ctx) => { const messages = await ctx.db.query('messages').order('desc').take(50); return messages.reverse(); }, });
-
Update
src/App.tsx
to UsegetMessages
query: Finally, updatesrc/App.tsx
to fetch and display messages using theuseQuery
hook and thegetMessages
query function. Replace the relevant section insrc/App.tsx
with the following code:// src/App.tsx import { useQuery, useMutation } from 'convex/react'; // ... other imports and component setup export default function App() { const messages = [ { _id: '1', user: 'Alice', body: 'Good morning!' }, { _id: '2', user: NAME, body: 'Beautiful sunrise today' }, ]; const messages = useQuery(api.chat.getMessages); // ... remaining component code }
-
Your
App.tsx
file should look like the following code after all updates:import { useEffect, useState } from 'react'; import { faker } from '@faker-js/faker'; import { api } from '../convex/_generated/api'; import { useQuery, useMutation } from 'convex/react'; const NAME = getOrSetFakeName(); export default function App() { const messages = useQuery(api.chat.getMessages); const sendMessage = useMutation(api.chat.sendMessage); const [newMessageText, setNewMessageText] = useState(''); useEffect(() => { setTimeout(() => { window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); }, 0); }, [messages]); return ( <main className="chat"> <header> <h1>Convex Chat</h1> <p> Connected as <strong>{NAME}</strong> </p> </header> {messages?.map((message) => ( <article key={message._id} className={message.user === NAME ? 'message-mine' : ''}> <div>{message.user}</div> <p>{message.body}</p> </article> ))} <form onSubmit={async (e) => { e.preventDefault(); await sendMessage({ user: NAME, body: newMessageText }); setNewMessageText(''); }} > <input value={newMessageText} onChange={async (e) => { const text = e.target.value; setNewMessageText(text); }} placeholder="Write a message…" autoFocus /> <button type="submit" disabled={!newMessageText}> Send </button> </form> </main> ); } function getOrSetFakeName() { const NAME_KEY = 'tutorial_name'; const name = sessionStorage.getItem(NAME_KEY); if (!name) { const newName = faker.person.firstName(); sessionStorage.setItem(NAME_KEY, newName); return newName; } return name; }
-
Your
convex/chat.ts
file should look like the following code after all updates:// convex/chat.ts import { query, mutation } from './_generated/server'; import { v } from 'convex/values'; export const sendMessage = mutation({ args: { user: v.string(), body: v.string(), }, handler: async (ctx, args) => { console.log('This TypeScript function is running on the server.'); await ctx.db.insert('messages', { user: args.user, body: args.body, }); }, }); export const getMessages = query({ args: {}, handler: async (ctx) => { const messages = await ctx.db.query('messages').order('desc').take(50); return messages.reverse(); }, });
-
Run the Convex chat application by executing the following command in your terminal within the
convex-tutorial
directory:npm run dev
Using the chat application
With the Convex chat application running and connected to your self-hosted Convex backend powered by Neon Postgres, you can now test the chat functionality.
-
Access the Chat application in your browser: Open your web browser and navigate tohttp://localhost:5173. You should see the Convex chat application interface.
-
Open a second browser window: Open a second browser window and navigate to http://localhost:5173.
-
Send and receive real-time messages: In one chat window, type and send a message. Verify that the message appears in real-time in both chat windows. Send messages from both windows and observe the bidirectional real-time updates.
Congratulations! You have successfully integrated Convex with Neon Postgres and implemented a real-time chat application using Convex queries and mutations.
Resources
- Convex documentation
- Convex self-hosting guide
- Neon documentation
- Neon Console
- Convex tutorial: A chat app
Need help?
Join our Discord Server to ask questions or see what others are doing with Neon. Users on paid plans can open a support ticket from the console. For more details, see Getting Support.