Tutorial – Setting up the server

Goal for this step 🏁: Have a working Dossier server in the backend.

In order to interact with the database we need a server.

  • npm install @dossierhq/server

The main thing we need to provide to the server is the database adapter. We also need to provide an authorization adapter, for now we'll go with a standard NoneAndSubjectAuthorizationAdapter. After the server is initialized we install a plugin (BackgroundEntityProcessorPlugin) that will validate and index entities in the background when the schema is modified.

To prepare for talking to the server we also initialize a Dossier client for an init user (both provider and identifier is up to our application).

In backend/server.ts:

import { type BetterSqlite3DatabaseAdapter } from '@dossierhq/better-sqlite3';
import {
BackgroundEntityProcessorPlugin,
NoneAndSubjectAuthorizationAdapter,
createServer,
type Server,
} from '@dossierhq/server';

async function initializeServer(logger: Logger, databaseAdapter: BetterSqlite3DatabaseAdapter) {
const serverResult = await createServer({
databaseAdapter,
logger,
authorizationAdapter: NoneAndSubjectAuthorizationAdapter,
});

if (serverResult.isOk()) {
const server = serverResult.value;

const processorPlugin = new BackgroundEntityProcessorPlugin(server, logger);
server.addPlugin(processorPlugin);
processorPlugin.start();
}

return serverResult;
}

export async function initialize(logger: Logger) {
const databaseResult = await initializeDatabase(logger);
if (databaseResult.isError()) return databaseResult;

const serverResult = await initializeServer(logger, databaseResult.value);
if (serverResult.isError()) return serverResult;
const server = serverResult.value;

const initSession = server.createSession({
provider: 'sys',
identifier: 'init',
defaultAuthKeys: [],
logger: null,
databasePerformance: null,
});
const client = server.createDossierClient(() => initSession);

return ok({ server });
}

const { server } = (await initialize()).valueOrThrow();

The server takes ownership of the database adapter, and when we're done with the server we need to call shutdown() so it can disconnect from the database. We also want to disconnect all connections to the HTTP server when shutting down the backend. In backend/main.ts:

const { server } = (await initialize(logger)).valueOrThrow();

const httpServer = app.listen(port, () => {
console.log(`Listening on http://localhost:${port}`);
});

process.once('SIGINT', shutdown);
process.once('SIGTERM', shutdown);
process.once('SIGUSR2', shutdown);

async function shutdown(signal: NodeJS.Signals) {
logger.info('Received signal %s, shutting down', signal);
httpServer.closeAllConnections();

if (shutdownResult.isError()) {
logger.error(
'Error while shutting down: %s (%s)',
shutdownResult.error,
shutdownResult.message
);
}

httpServer.close((error) => {
if (error) {
logger.error('Error while shutting down: %s', error.message);
}
logger.info('Backend shut down');
process.kill(process.pid, signal);
});
}

Now when we make changes to the backend, we will get a log like this when nodemon restarts the backend:

[be] info: Received signal SIGUSR2, shutting down
[be] info: Shutting down database adapter
[be] info: Finished shutting server
[be] info: Backend shut down
[be] info: Loading schema
[be] info: No schema set, defaulting to empty
[be] Listening on http://localhost:3000