Getting Started
Demo

Demo

These steps help you setup a NodeJS TypeScript backend server and a TypeScript React frontend from scratch. If already have these and simply want to add Krmx to them, then you can follow the installation instructions.

Prerequisites

To be able to follow this demo you need the following tools installed on your system.

  • npm (recommended version 18.x or higher)
  • a code editor (such as Visual Studio Code or IntelliJ)
  • if your system doesn't have touch, then install it using npm install touch-cli -g

Note: This demo is created on a macOS system, but it is intended to be compatible with Linux or Windows systems.

Application Directory

First create a directory for our application.

mkdir my-first-krmx-app
cd my-first-krmx-app
 
# optional create a git repository for your application
git init

Create a Krmx server

Now, we're going to set up a NodeJS TypeScript server using the @krmx/server (opens in a new tab) reference implementation.

mkdir server
cd server
npm init -y
npm install --save-dev ts-node @types/node typescript nodemon
npm install @krmx/server
touch server.ts

Then, in your server/package.json add the following script. This script will start your server in dev mode. It will restart your server any time it detects changes to the server.ts file.

...
"scripts": {
  "dev": "nodemon --exec \"ts-node server.ts\" server.ts"
}
...

Then, add the following code to your server/server.ts file.

import { createServer, Props } from '@krmx/server';
 
const props: Props = { /* configure here */ }
const server = createServer(props);
 
server.on('join', (username) => {
  console.debug(`[debug] [my-app] ${username} joined!`);
});
server.on('message', (username, message) => {
  console.debug(`[debug] [my-app] ${username} sent ${message.type}`);
});
 
server.listen(8082);

Now start your server with npm run dev. Everytime you make changes to your server.ts file, the server will automatically reload.

If your server is up and running, Krmx clients should be able to connect using ws://localhost:8082 on your local machine.

If you would like to version control your application using git, then include the following .gitignore file in the server/ directory:

# in server/.gitignore
node_modules

To continue with the demo, you need to keep the server running. So before you continue, open a new terminal first.

Create a Krmx client

Next, we're going to set up a React client using the @krmx/client-react (opens in a new tab) reference implementation.

cd my-first-krmx-app
npx create-next-app client --use-npm --ts --src-dir --eslint --tailwind --app --import-alias '@/*'
cd client
npm install @krmx/client-react

Then, you can create a simple React client using the following setup in client/src/app/page.tsx.

"use client";
import { useEffect, useState } from 'react';
import { createClient, createStore } from '@krmx/client-react';
 
// Create the client
export const { client, useClient } = createClient();
 
// Create an example store for messages received from the server
let id = 0;
export const useMessages = createStore(
  client,
  /* the initial internal state */
  [] as { id: number, message: string }[],
  /* a reducer handling state changes based on incoming messages */
  (state, message) => {
    id += 1;
    return [...state, { id, message: message.type }].slice(-10)
  },
  /* a mapper that can map the internal state to a different format */
  s => s
);
 
// The example client component that you can use in your React app to connect to a Krmx server
export function KrmxExampleClient({ serverUrl }: { serverUrl: string }) {
  // Use the Krmx client and the example message store in this component
  const { status, username, users } = useClient();
  const messages = useMessages();
 
  // Keep track of failures
  const [failure, setFailure] = useState<string | null>(null);
  useEffect(() => { setFailure(null); }, [status]);
 
  // When the server url changes, disconnect the client from the server
  useEffect(() => {
    if (client.getStatus() !== 'initializing' && client.getStatus() !== 'closed') {
      client.disconnect(true)
        .catch((e: Error) => console.error('error disconnecting', e.message));
    }
 
    // And... disconnect from the server when the component unmounts
    return () => {
      if (client.getStatus() !== 'initializing' && client.getStatus() !== 'closed') {
        client.disconnect(true)
          .catch((e: Error) => console.error('error disconnecting', e.message));
      }
    };
  }, [serverUrl]);
 
  // Your logic for when you're not (yet) connected to the server goes here
  if (status === 'initializing' || status === 'connecting' || status === 'closing' || status === 'closed') {
    return <>
      <h2>Status: {status}</h2>
      <p>No connection to the server...</p>
      <button onClick={() => {
        client.connect(serverUrl)
          .catch((e: Error) => setFailure(e.message));
      }}>
        Connect to {serverUrl}.
      </button>
    </>;
  }
 
  // Your logic for when your connection is not (yet) linked to a user goes here
  if (status === 'connected' || status === 'linking' || status === 'unlinking') {
    return (
      <div>
        <h2>Status: {status}</h2>
        <button onClick={() => {
          client.link('simon-' + Math.random().toString(36).slice(2, 6), 'no-auth')
            .catch((e: Error) => setFailure(e.message))
        }}>
          Link!
        </button>
        {failure && <p>Rejected: {failure}</p>}
        <button onClick={() => {
          client.disconnect()
            .catch((e: Error) => setFailure(e.message));
        }}>
          Disconnect!
        </button>
      </div>
    );
  }
 
  // Your logic for when you're ready to go goes here
  return (
    <div>
      <h2>Status: {status}</h2>
      <p>Welcome, <strong>{username}</strong>!</p>
      <button onClick={() => client.send({ type: 'custom/hello' })}>
        Send custom/hello
      </button>
      <button onClick={() => client.unlink().catch((e: Error) => setFailure(e.message))}>
        Unlink
      </button>
      <button onClick={() => client.leave().catch((e: Error) => setFailure(e.message))}>
        Leave
      </button>
      <h2>Users</h2>
      <ul>
        {users.map(({ username: otherUsername, isLinked }) => (
          <li key={otherUsername}>
            {isLinked ? '🟢' : '🔴'} {otherUsername}
          </li>),
        )}
      </ul>
      <ul>
        {messages.map(({ id, message }) => <li
          key={id}
        >{message}</li>)}
      </ul>
    </div>
  );
}
 
export default function MyApp() {
  return <>
    <h1>Krmx Example Client</h1>
    <KrmxExampleClient serverUrl="ws://localhost:8082" />
  </>;
}

Now you can run npm run dev in the client/ directory to start your client. Then, navigate to http://localhost:3000 to view the result. If your server is still running, you should be able to connect to it.

Next steps

After following this demo we recommend you to try the following.

  • Try and play around with what you have just created.
    • How do you make sure that you can fill in your own name when joining?
    • How to allow a maximum of 4 users on a server?
    • How to move the server to a different port?
    • How to split the App.tsx into multiple files?
    • How to make sure that only authenticated users can join?
    • How to send custom messages and use these in your clients using a custom store?
  • Look at some reference implementations.
  • Learn more about the Krmx API.
  • Learn more about Krmx State to handle advance state management for you.