Using PostGraphile as a Library
Library mode is the most popular way of running PostGraphile; it gives more power than using the CLI (see CLI usage) because you can leverage the capabilities and ecosystems of your chosen Node.js webserver (Express, Koa, Fastify, etc), but is more fully featured than Schema-only Usage.
PostGraphile instance
Library mode is configured using a preset (see Configuration for
the options) and returns a PostGraphile instance pgl
which has various
methods you can use depending on what you're trying to do.
import preset from "./graphile.config.js";
import { postgraphile } from "postgraphile";
// Our PostGraphile instance:
export const pgl = postgraphile(preset);
pgl.createServ(grafserv)
Grafserv supports a number of different servers in the JS ecosystem, you
should import the grafserv
function from the relevant grafserv subpath:
import { grafserv } from "postgraphile/grafserv/express/v4";
// OR: import { grafserv } from "postgraphile/grafserv/node";
// OR: import { grafserv } from "postgraphile/grafserv/koa/v2";
// OR: import { grafserv } from "postgraphile/grafserv/fastify/v4";
Then create your serv
instance by passing this to the pgl.createServ()
method:
const serv = pgl.createServ(grafserv);
This Grafserv instance (serv
) can be mounted inside of your chosen server -
for instructions on how to do that, please see the relevant entry for your
server of choice in the Grafserv
documentation; typically there's a
serv.addTo(...)
method you can use.
Here's an example with Node's HTTP server:
import { createServer } from "node:http";
import { grafserv } from "postgraphile/grafserv/node";
import { pgl } from "./pgl.js";
const serv = pgl.createServ(grafserv);
const server = createServer();
server.once("listening", () => {
server.on("error", (e) => void console.error(e));
});
serv.addTo(server).catch((e) => {
console.error(e);
process.exit(1);
});
server.listen(5678);
console.log("Server listening at http://localhost:5678");
And an example for Express:
import { createServer } from "node:http";
import express from "express";
import { grafserv } from "postgraphile/grafserv/express/v4";
import { pgl } from "./pgl.js";
const serv = pgl.createServ(grafserv);
const app = express();
const server = createServer(app);
server.once("listening", () => {
server.on("error", (e) => void console.error(e));
});
serv.addTo(app, server).catch((e) => {
console.error(e);
process.exit(1);
});
server.listen(5678);
console.log("Server listening at http://localhost:5678");
For information about using this serv
instance with Connect, Express, Koa, Fastify,
Restify, or any other HTTP servers, please see the Grafserv
documentation.
pgl.getSchemaResult()
Returns a promise to the schema result — an object containing:
schema
- the GraphQL schemaresolvedPreset
- the resolved preset
Note that this may change over time, e.g. in watch mode.
pgl.getSchema()
Shortcut to (await pgl.getSchemaResult()).schema
— a promise to the GraphQL
schema the instance represents (may change due to watch mode).
pgl.getResolvedPreset()
Get the current resolved preset that PostGraphile is using. Synchronous.
pgl.release()
Call this when you don't need the PostGraphile instance any more and it will release any resources it holds (for example schema watching, etc).
Example 1: single JS file, express
In this example we'll set up a PostGraphile server using ExpressJS all in a
single JS file that we can run directly with node
.
To start, create a directory for the project and initialize a Node.js project in that folder:
mkdir postgraphile_express
cd postgraphile_express
npm init -y
Installing dependencies
Install the required packages:
- npm
- Yarn
- pnpm
npm install --save express postgraphile@beta
yarn add express postgraphile@beta
pnpm add express postgraphile@beta
Environment variables
It's bad practice to store credentials directly into source code, so instead we
use an environment variable DATABASE_URL
to to store the database connection
string.
Additionally, we want PostGraphile to behave slightly differently in
development versus production: in development we want better error messages and
easier to read SQL; whereas, in production, we don't want to reveal more
information to potential attackers than we need to, and we want to execute as
fast as possible. Since we're in development, we'll use the
GRAPHILE_ENV=development
envvar to indicate this.
To save us from having to pass the environment variables every time we run the server,
we can add them to a convenient .env
file:
# Replace the contents of the square brackets with the details of your database
DATABASE_URL=postgres://[username]:[password]@[host]:[port]/[database]
GRAPHILE_ENV=development
Be sure to replace the square brackets with the relevant settings for your own database connection!
Critically, we must ensure that this file is not tracked by git:
echo .env >> .gitignore
The code
Create a server.js
file with the following contents:
import express from "express";
import { createServer } from "node:http";
import { postgraphile } from "postgraphile";
import { PostGraphileAmberPreset } from "postgraphile/presets/amber";
import { makePgService } from "postgraphile/adaptors/pg";
import { grafserv } from "postgraphile/grafserv/express/v4";
// Which port do we want to listen for requests on?
const PORT = 5050;
// Our PostGraphile configuration, we're going (mostly) with the defaults:
/** @type {GraphileConfig.Preset} */
const preset = {
extends: [PostGraphileAmberPreset],
pgServices: [
makePgService({
connectionString: process.env.DATABASE_URL,
schemas: ["public"],
}),
],
grafast: {
explain: true,
},
};
// Create our PostGraphile instance, `pgl`:
const pgl = postgraphile(preset);
// Create our PostGraphile grafserv instance, `serv`:
const serv = pgl.createServ(grafserv);
async function main() {
// Create an express app:
const app = express();
// Create a Node HTTP server, and have the express app handle requests:
const server = createServer(app);
// If the server were to produce any errors after it has been successfully
// set up, log them:
server.once("listening", () => {
server.on("error", (e) => void console.error(e));
});
// Mount our grafserv instance inside of the Express app, also passing the
// reference to the Node.js server for use with websockets (for GraphQL
// subscriptions):
await serv.addTo(app, server);
// Start listening for HTTP requests:
server.listen(PORT, () => {
console.log(`Server listening at http://localhost:${PORT}`);
});
}
// Start the main process, exiting if an error occurs during setup.
main().catch((e) => {
console.error(e);
process.exit(1);
});
The project is now complete, listing the project directory should show
postgraphile_express/
├── .env
├── .gitignore
├── node_modules/
├── package-lock.json
├── package.json
└── server.js
The package.json
file should have the content similar to the following (with
the versions of express
and postgraphile
being the most recent):
{
"name": "postgraphile_express",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "^4.21.2",
"postgraphile": "^5.0.0-beta.38"
}
}
To tidy up, let's make the following changes to the package.json
file:
{
"name": "postgraphile_express",
"version": "1.0.0",
- "main": "index.js",
+ "private": true,
+ "type": "module",
"scripts": {
+ "start": "node --env-file=./.env server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
- "keywords": [],
- "author": "",
- "license": "ISC",
- "description": "",
"dependencies": {
"express": "^4.21.2",
"postgraphile": "^5.0.0-beta.38"
}
}
The main things we've done here are:
- Remove the reference to a non-existant index.js file
- Mark the project as "private" such that we can't attempt to
npm publish
it to the npm repository - Declare that
.js
files are ES modules, enabling the use ofimport
statements - Delete unnecessary metadata
- Add our
start
script, which runs the server contained inserver.js
using thenode
command, passing the environment variables stored in the.env
file
Running the server
Thanks to the start
script we added above, running our server is as simple as:
npm start
Alternatively you can run the command from the start script directly:
node --env-file=./.env server.js
Either way, this will start a server at http://localhost:5050
. Opening the
URL in a browser will show a user interface where a GraphQL query can be
entered. The following query is valid against any GraphQL schema and tells you
the fields that are available to be queried at the Query
root:
query {
__schema {
queryType {
name
fields {
name
description
}
}
}
}
Example 2: TypeScript project, express
To get the most out of PostGraphile (and the modern npm ecosystem in general), you'll want to be running TypeScript. This will give you much better auto-complete in your editor, type safety of your code, help with refactoring, and so much more.
This example will have three files in a src
directory: graphile.config.ts
,
pgl.ts
and server.ts
. It will also demonstrate including the
simple-inflection
preset
, included
as PgSimplifyInflectionPreset
in graphile.config.ts
To start, create a directory for the project and initialize a Node.js project in that folder:
mkdir postgraphile_express_typescript
cd postgraphile_express_typescript
npm init -y
Installing dependencies
Install the required packages:
- npm
- Yarn
- pnpm
npm install --save express postgraphile@beta @graphile/simplify-inflection@beta
npm install --save-dev typescript @tsconfig/node22 @types/express @types/node
yarn add express postgraphile@beta @graphile/simplify-inflection@beta
yarn add --dev typescript @tsconfig/node22 @types/express @types/node
pnpm add express postgraphile@beta @graphile/simplify-inflection@beta
pnpm add --save-dev typescript @tsconfig/node22 @types/express @types/node
Environment variables
As before, create a .env
file containing your database connection string and
development/production environment setting:
DATABASE_URL=postgres://[username]:[password]@[host]:[port]/[database]
GRAPHILE_ENV=development
And ensure that it is not tracked by git:
echo .env >> .gitignore
TypeScript configuration
Create a tsconfig.json
file that extends from the relevant @tsconfig for your
Node.js major version; e.g. if you're using Node v22.14.0 that would be
@tsconfig/node22
:
{
"extends": "@tsconfig/node22/tsconfig.json",
"compilerOptions": {
"erasableSyntaxOnly": true,
"rewriteRelativeImportExtensions": true,
"rootDir": "./src",
"outDir": "./dist"
}
}
The code
In the postgraphile_express_typescript
directory, create a src
directory and
create three TypeScript files in the src folder: graphile.config.ts
, pgl.ts
and server.ts
with the contents shown below:
src/graphile.config.ts
import type {} from "graphile-config";
import "postgraphile";
import { makePgService } from "postgraphile/adaptors/pg";
import { PostGraphileAmberPreset } from "postgraphile/presets/amber";
import { PgSimplifyInflectionPreset } from "@graphile/simplify-inflection";
const preset: GraphileConfig.Preset = {
extends: [PostGraphileAmberPreset, PgSimplifyInflectionPreset],
pgServices: [
makePgService({
connectionString: process.env.DATABASE_URL,
schemas: ["public"],
}),
],
grafast: {
explain: true,
},
};
export default preset;
src/pgl.ts
import { postgraphile } from "postgraphile";
import preset from "./graphile.config.ts";
export const pgl = postgraphile(preset);
.ts
along with rewriteRelativeImportExtensions
With Node's new --experimental-strip-types
flag, TypeScript syntax is removed
so that the TS can be executed directly as if it were JS. However, the files
still need to be able to reference each other. In the source code, that means
referencing the .ts
file; but when TypeScript compiles the code for
production the output will be .js
files.
Fortunately, TypeScript has added the configuration option
rewriteRelativeImportExtensions
to ensure that you can use .ts
to reference
TypeScript files in your source code whilst still ensuring that these imports
are output as .js
when compiled for production usage.
src/server.ts
import { createServer } from "node:http";
import express from "express";
import { grafserv } from "postgraphile/grafserv/express/v4";
import { pgl } from "./pgl.ts";
const serv = pgl.createServ(grafserv);
const app = express();
const server = createServer(app);
server.once("listening", () => {
server.on("error", (e) => void console.error(e));
});
serv.addTo(app, server).catch((e) => {
console.error(e);
process.exit(1);
});
server.listen(5050);
console.log("Server listening at http://localhost:5050");
Contents of package.json
After making similar changes to package.json
as we did with Example 1 above, we should
end up with a package.json
file that looks something like:
{
"name": "simple_node_project",
"version": "1.0.0",
+ "private": true,
+ "type": "module",
"scripts": {
+ "start": "node --env-file=./.env src/server.ts",
+ "build": "tsc",
+ "prod": "node dist/server.js"
},
"dependencies": {
"@graphile/simplify-inflection": "^8.0.0-beta.6",
"express": "^4.21.2",
"postgraphile": "^5.0.0-beta.38"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.0",
"@types/express": "^5.0.0",
"@types/node": "^20.11.24",
"typescript": "^5.7.3"
}
}
Note that we have three scripts:
start
runs our source files directly using Node 22's type strippingbuild
compiles our.ts
files to.js
files to run in productionprod
runs the compiled.js
files in production, and expects the environment variables to already be set in the environment rather than reading them from a file
Project Structure
The project structure should be
postgraphile_express_typescript/
├── .env
├── .gitignore
├── node_modules/
├── package-lock.json
├── package.json
├── src/
│ ├── graphile.config.ts
│ ├── pgl.ts
│ └── server.ts
└── tsconfig.json
Development
In development, run npm start
as before. This will run the source code
directly using Node's native type stripping feature. This will start a server
at http://localhost:5050
, opening the url in a browser will show a user
interface where a GraphQL query can be entered, for example
query {
__schema {
queryType {
name
fields {
name
description
}
}
}
}
npm start
?If you get errors when running npm start
it might be because you are not
running a sufficiently up to date version of Node.js, lacking the type
stripping features. If this is the case, you'll want to run TypeScript in watch
mode (yarn tsc --watch
) in one terminal, and then execute the compiled JS
code directly: npde --env-file=./.env dist/server.js
.
Production
In production, we want to minimize overhead. To do so, we compile the
TypeScript to JavaScript up front by running the yarn build
command. Then we
ship the resulting files (including the dist/
folder) to production and we
run the npm run prod
command which executes the compiled code directly.