Set up the backend
Learn how to set up the backend of a Weave.js app on Next.js
In this guide, you will learn how to set up the backend infrastructure for a collaborative application using Weave.js.
To make the backend functional, we need to set up a Next.js custom server to later integrate Weave.js Websockets store.
The store will handle the shared-state management, client networking, persistence, and awareness events.
Prerequisites
Before you begin, ensure that you have completed the manual installation guide up to the "Set up the backend project" step.
Step by step
To set up Weave.js backend over our Next.js project (on a single artifact), follow these steps:
Install the Weave.js backend dependencies
First, let's install the Weave.js websocket store dependencies.
On your root project folder, execute the following command:
npm install ws @inditextech/weave-store-websockets
Set up the Next.js custom server
To have a frontend and backend on the same artifact on top of Next.js we need to implement a custom server on Next.js, and later on, include the necessary Weave.js backend logic.
Not recommended for production
For production environments, you should consider using a separate backend server to handle the Weave.js backend logic.
For more details, refer to the backend showcase implementation.
Dependencies installation
To start, install the necessary dependencies for the custom server with the following commands:
- Dependencies needed on runtime:
npm install cross-env express
- Dependencies only needed on the local development environment or build time:
npm install -D ts-node nodemon @types/express
TypeScript configuration
Nodemon
This section uses nodemon, a tool that helps to automatically start the underlying Node.js application when a file changes.
Then, set up the TypeScript configuration for the server part:
-
Create a
tsconfig.server.json
file on the root of your project with the following content:tsconfig.server.json { "extends": "./tsconfig.json", "compilerOptions": { "module": "commonjs", "outDir": "dist", "lib": ["es2019"], "target": "es2019", "isolatedModules": false, "noEmit": false }, "include": ["server.ts"] }
-
Set up the transpilation of the server files from TypeScript to JavaScript. Create a
nodemon.json
file on the root of your project with the following content:nodemon.json { "watch": ["**/*.{ts,tsx}", "next.config.mjs", "persistence.ts", "server.ts"], "ignore": ["node_modules/**/*"], "exec": "ts-node --project tsconfig.server.json server.ts", "ext": "js ts" }
Next.js configuration
Then, set up the Next.js configuration:
-
Create a
next.config.mjs
file on the root of your project with the following content:next.config.mjs /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: false, experimental: { serverComponentsExternalPackages: ["yjs"] }, }; export default nextConfig;
Custom server logic
Express
This section uses Express, a web framework for Node.js, as the underlying server for the custom server.
Define the custom server logic by creating a file named server.ts
on the root of your project with the following content:
import express, { Request, Response } from "express";
import next from "next";
const port = parseInt(process.env.PORT || "3000", 10);
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const app = express();
app.get("/{*splat}", (req: Request, res: Response) => {
return handle(req, res);
});
app.listen(3000, (err: Error | undefined) => {
if (err) throw err;
console.log(
`> Server listening at http://localhost:${port} as ${
dev ? "development" : process.env.NODE_ENV
}`
);
});
});
Custom server tooling
To properly use the custom server both locally and during the build process, we need to customize the dependent scripts in your package.json
: specifically the dev
, build
, and start
scripts.
Change the scripts definitions with the following content:
{
...
"scripts": {
...
"dev": "next dev --turbo",
"dev": "nodemon",
"build": "next build",
"build": "next build && tsc --project tsconfig.server.json",
"start": "next start",
"start": "cross-env NODE_ENV=production node dist/server.js"
...
},
...
}
Finally, test that the Next.js project keeps starting with the new custom server configuration.
On the project root folder, run the following command:
npm run dev
Navigate to http://localhost:3000
on a browser.
You should still see the Next.js default application running and the console should have no errors at all.
Integrate Weave.js into the backend
Now that the Next.js custom server is completely set up, let's add the necessary Weave.js backend logic.
For that, we will use the Websockets store library: (@inditextech/weave-store-websockets
).
We need to:
- Define how to handle the persistence of the shared-state.
- Instantiate the WeaveWebsocketsServer class from the library
@inditextech/weave-store-websockets
and configure it.
Set up the shared-state persistence handlers
In this step, we will define the persistence logic for the shared-state. In this case, we use the file system as our persistence layer.
Create a persistence.ts
file on the project root and set its content to:
import path from "path"; // (1)
import fs from "fs/promises"; // (1)
export const fetchRoom = async (docName: string) => {
try {
const roomsFolder = path.join(__dirname, "rooms");
const roomsFile = path.join(roomsFolder, docName);
return await fs.readFile(roomsFile);
} catch (e) {
return null;
}
}; // (2)
export const persistRoom = async (
docName: string,
actualState: Uint8Array<ArrayBufferLike>
) => {
try {
const roomsFolder = path.join(__dirname, "rooms");
let folderExists = false;
try {
await fs.access(roomsFolder);
folderExists = true;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
folderExists = false;
}
if (!folderExists) {
await fs.mkdir(roomsFolder, { recursive: true });
}
const roomsFile = path.join(roomsFolder, docName);
await fs.writeFile(roomsFile, actualState);
} catch (ex) {
console.error(ex);
}
}; // (3)
File explanation:
(1)
: Import necessary Node.js dependencies to handle the file system.(2)
:fetchRoom
function fetches a previously saved shared-state of a room by its ID from the file system.(3)
:persistRoom
function persist the shared-state of a room by its ID onto the file system.
Set up the Weave.js Websockets store
Finally, set up the Weave.js store handler. In this case, we use the Websockets transport.
On the custom server file server.ts
, add the necessary logic to use the @inditextech/weave-store-websockets
store handler.
You can replace the contents of server.ts
with this content:
import { IncomingMessage } from "http";
import express, { Request, Response } from "express";
import next from "next";
import { WeaveWebsocketsServer } from "@inditextech/weave-store-websockets/server"; // (1)
import { fetchRoom, persistRoom } from "./persistence"; // (2)
const VALID_ROOM_WEBSOCKET_URL = /\/sync\/rooms\/(.*)/; // (3)
const port = parseInt(process.env.PORT || "3000", 10);
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const app = express();
app.get("/{*splat}", (req: Request, res: Response) => {
return handle(req, res);
});
app.listen(3000, (err: Error | undefined) => {
const server = app.listen(3000, (err: Error | undefined) => {
if (err) throw err;
console.log(
`> Server listening at http://localhost:${port} as ${
dev ? "development" : process.env.NODE_ENV
}`
);
}); // (4)
const weaveWebsocketsServerConfig = {
performUpgrade: async (request: IncomingMessage) => {
return VALID_ROOM_WEBSOCKET_URL.test(request.url ?? "");
}, // (6)
extractRoomId: (request: IncomingMessage) => {
const match = request.url?.match(VALID_ROOM_WEBSOCKET_URL);
if (match) {
return match[1];
}
return undefined;
}, // (7)
fetchRoom, // (8)
persistRoom, // (9)
}; // (5)
const wss = new WeaveWebsocketsServer(weaveWebsocketsServerConfig); // (10)
wss.handleUpgrade(server); // (11)
});
Changes explanation:
(1)
: Import Weave.js Websockets store dependency.(2)
: Import persistence handlers previously defined.(3)
: Define a regex to identify the Weave.js Websockets connection URI.(4)
: Extract the underlying HTTP server.(5)
: Define the configuration for the WeaveWebsocketsServer class.(6)
: Define the propertyperformUpgrade
, this indicates which URIs are valid for the Websocket upgrade protocol (upgrade from HTTP to WEBSOCKETS)(7)
: Define the propertyextractRoomId
, this indicates how to extract the room ID from the Websocket connection URI.(8)
: Use the previousfetchRoom
function for persistence management.(9)
: Use the previouspersistRoom
function for persistence management.(10)
: Instantiate the WeaveWebsocketsServer class.(11)
: Attach the Weave.js Websockets server to the HTTP server.
Run the project
Finally, test that the project keeps starting with the changes made to the custom server.
On the project root folder, run the following command:
npm run dev
Navigate to http://localhost:3000
on a browser.
You should still see the Next.js default application running and the console should have no errors at all.
Next steps
Let's now set up the frontend of the application.