5: Add a real database
5: Add a real database
In the tutorial so far, storage of user and credential information has been handled in a local SQLite database, for the sake of a dependency-free setup. For real production readiness, the app needs to interface with a more robust database service capable of supporting a serverless application where the filesystem is ephemeral.
In this exercise, you’ll build a second storage driver for Postgres. This database solution was chosen because of the ease of configuring a Postgres integration on Vercel, and we’ll also cover the basic steps for deploying to this hosting provider.
Related environment variables
For reference, you can note the following vars/values in .env.example to prepare for this exercise.
Unless you’re going to connect to a real Postgres database in your local development environment, you should not set these
vars in .env.local. The Vercel deployment step will include a reminder for setting these vars in the deployed environment.
Build the Postgres credentials store driver
The first step is the heavy grunt work of defining the driver, with its read/write operations conforming to your existing
CredentialsStore interface. This is all unremarkable SQL operations very similar to the SQLite driver.
One area that differs from SQLite is the need for a connection “pool”, handled with the pg libary’s Pool constructor.
Create the connection pool helper
Replace the contents of src/lib/credentials-store/postgres-driver/get-pool.ts.
DATABASE_POOL_MAX matters most if you’re pointing this at a plain, unpooled Postgres server rather than a pooled endpoint like Neon’s.
node-postgres’s own default (10 connections) is safe against a pooler, which fans many concurrent queries in below that limit. Against
a server with no pooler in front of it, lower it to somewhere in the 2–5 range.
Implement the Postgres driver
Replace the contents of src/lib/credentials-store/postgres-driver/postgres-credentials-store.ts.
PostgresCredentialsStore implements the exact same CredentialsStore interface as SqliteCredentialsStore from the previous exercise.
That shared interface is the seam: nothing else in the app — the install callback, the launch callback, the authorization checks — knows
or cares which driver is actually storing data. Once CREDENTIALS_STORE_DRIVER selects POSTGRES (in subsequent steps below),
everything upstream of get-credentials-store.ts keeps working unchanged.
Access tokens are still encrypted at rest via CREDENTIALS_ENCRYPTION_KEY, exactly as with the SQLite driver — setStore encrypts before writing, getStoreToken decrypts after reading. The Postgres driver introduces no new handling for this; it reuses the same encrypt/decrypt helpers.
Build the Postgres driver loader indirection
This step isn’t strictly necessary if Postgres will always be the supporting storage solution in your deployed environment. However,
in this app the goal is to keep hosting target flexible, including support for multiple database drivers. If the app is deployed to
an environment where Postgres is not the configured storage, the dependency imports in postgres-credentials-store.ts could cause
a build problem.
The specifics have to do with the optional dependency pg-cloudflare, missing from the app’s default dependencies but assumed at runtime
in a Cloudflare Workers deployment. Even if Postgres is not the intended storage solution in a Cloudflare-deployed scenario, directly
importing postgres-credentials-store.ts (with its own chain of dependency imports) will confuse the bundling process into an error.
To keep the app flexible for non-Postgres environments, this step introduces an indirection technique creating a different execution path in those scenarios.
Create the stable loader specifier
Instead of directly importing postgres-credentials-store.ts, the runtime “switcher” that chooses a driver based on CREDENTIALS_STORE_DRIVER
will use this thin loader.
Replace the contents of src/lib/credentials-store/postgres-driver-loader.ts with a simple re-export of PostgresCredentialsStore.
Write the initial Postgres migration and runner
The SQLite driver relies on running CREATE TABLE IF NOT EXISTS on each connection. In a real production environment, you need
a more optimized strategy. In this step, you’ll create a Postgres “migration” script that runs on each deployment to keep your
database schema up to date.
Write the initial schema migration
Create src/lib/credentials-store/postgres-driver/migrations/0001_initial_schema.sql with the following contents.
Note that is a rare case in the tutorial in which the file does not already exist in the project boilerplate.
Create the migration runner script
Replace the contents of scripts/postgres/migrate.mjs.
This short command-line script uses the node-pg-migrate library to handle running all pending Postgres migrations.
Wire the migration into the build
Modify package.json to add the migration script and a vercel-build script that runs it before every build.
You’re building these scripts directly into package.json, assuming Vercel as the deployment target.
In keeping with the aim of hosting provider flexibility, the final version of this app instead includes a scaffold script
that will modify package.json on demand when Vercel is the target platform. See the post-tutorial enhancements.
Add the POSTGRES driver-select branch
The final step is to update getCredentialsStore to select the Postgres driver when CREDENTIALS_STORE_DRIVER is POSTGRES.
Your app is now ready for an environment with Postgres connected.
Full step code
Deploy to Vercel with Postgres
The starter app is designed to support Vercel as a default hosting target. See Deploying to Vercel in the repo documentation for a full walkthrough of the deployment process, but below are the basic steps.
Note that the “Scaffold the Vercel Tooling” step in the project guide is not necessary for your tutorial codebase, where the db:postgres:migrate
and vercel-build scripts have already been baked into package.json.
Prerequisites
- A Vercel account
- An account with a supported Git provider like GitHub (see Deploying Git Repositories with Vercel for details. You can connect your Git account during project setup.)
Steps
Once you create your Vercel project: If BigCommerce needs to reach a preview or branch deployment rather than production, disable or scope Vercel’s Deployment Protection for it under Project Settings > Deployment Protection. Otherwise BigCommerce’s server-to-server callbacks hit Vercel’s SSO gate instead of your app, and installs fail in a way that looks like an app bug rather than a deployment-configuration one.
- Commit your current code to Git and push to a remote repository on your chosen Git provider.
- Create a new project in Vercel and import from your Git provider. If any environment variables are auto-populated from the project, delete them. You initially just need one: Set the environment variable
DATA_MODEtoMOCKfor an initial deployment. - Verify the deployment renders in
MOCKmode and capture the deployed base URL. - Create a Neon Postgres database from the project’s Storage tab, which automatically provisions
DATABASE_URLandDATABASE_URL_UNPOOLEDvalues in the project. - Create a new app in the Developer Portal and set the callback URLs using the Vercel production URL. (See the previous tutorial step or the guide linked above for other required settings.)
- Update/set the remaining vars in the Vercel project’s Environment Variables tab. See the table below.
- Redeploy.
Your Vercel project has a permanent domain that remains unchanged across deployments and a deployment domain with a deployment-specific hash.
Make sure to use the permanent domain for APP_ORIGIN and in your Developer Portal callback URLs.
You should now have a Vercel-hosted app ready to install in your store’s control panel (Apps -> Develop)!