Writing a Modern MUSH Server with Typescript Part 1: Overview and Setup
Tags: tutorial, typescript, node, mush
Published: 2020-03-12 00:18:49

## Introduction
For a while now, I've wanted to start writing tutorials and create my own MUSH server with one of my new favorite language (flavors), Typescript! In this project, we'll learn a little bit of everything.  While it won't be a comprehensive guide to the technologies - We'll be working with things like Typescript, TS-Node, Express, a NoSQL database, and Socket.io.  We'll also have to make a simple client to our server.

### Disclaimer!
There has been a real renaissance of interest in the modernization of servers written in C/C++ out there that are now, twenty or thirty years old. That's amazing! I grew up playing on these platforms, and they will forever hold a special place in my heart. There are other platforms out there that have sprung up in as a new generation of MUSHING.  Two of the most notable are  [AresMUSH](www.aresmush.com) written in Ruby, and [Evennia](www.evennia.com) written in Python. Both really approachable languages, and both really excellent options!  There is, of course, always room for another project!

### Update! (3/23/20)
UrsaMU has become my quarantine sanity project, so I've revised it's inner workings some.  I'll be slowly moving through the articles I've written to update the code portions to reflect the current build. I flattened the API some - I might even put it into a facade if it starts feeling cumbersome. So, sorry to any of you that were coding along with me, and thanks for the patience! :)

## Enter UrsaMU: Project Set up
First things first, we need to make a new folder and set up our project with a package.json.  You'll need to make sure that you have **node + npm** installed in your development environment. For this project, I'll be using **Ubuntu 18.04** (through the Windows 10 WSL2) and **VS Code** as my Typescript IDE.

## What is a MUSH?
Let's take a moment to discuss what a **MUSH** actually is, or, what *I* interpret a MUSH to be.  At its core, a MUSH is a chat server backed by a database of some sort the persistence of in-game objects traditionally consisting of rooms, things, players and exits.  When a user types input into the game, it's piped through a series of middlewares to evaluate for in-game actions, evaluating them and returning output to all involved parties.  Another key selling point for MUSH is its ability for a user to create custom in-game commands and functions that are saved to specially keyed attributes.  This allows for things like creating rooms, objects, exits, etc, and have all of the changes persisted in the database.

### A MUSH is a Chat Server At It's Core.
This is where Socket.io comes into play.  It'll act as the backbone of our chat server, while also handling things like 'rooms' and 'channels'.

### A MUSH Is Backed By A Database for Persistence
For my purposes, I'm going to use a file-based database, [NeDB](https://github.com/louischatriot/nedb).  However, I don't want to limit what kind of database a game uses, or how they choose to store their data so we will focus on creating an adapter for all of the basic commands we'll need for our model (Create, Find, Get, Update, and Delete).  We also want to allow other implementations to easily add functionality on top of our initial needs.

### A MUSH Evaluates User Input for In-Game Actions.
This will be the primary focus of **UrsaMajor**.  When an input is sent it needs to be evaluated as 'just a message', or if it has in-game significance, like an expression to evaluate, or some special action to take to affect the in-game environment.  We will create a middleware system to handle user input, passing responsibility for the input down the line, function by function.

### A MUSH Allows For In-Game Creation On The Fly
During our evaluation process, we'll have to check for commands that have been set on in-game objects. This probably means writing a simple algorithm to handle how actions are parsed, and mushcode expressions evaluated.

## Planned Features

The working list for basic server features.  **UrsaMU** Is still under extreme development, so this list is subject to shrink (Or grow!):

- **Database**: Handle your data however you want! **UrsaMU** allows you to bring your favorite database to the table with its **database adapter API**.
- **Commands**: Enter your own custom commands through the **command API**.
- **Flags** The game's flags are editable from either the **Flag API** or through stored JSON flat files - or both!
- **MushCode**: Evaluate mushcode expressions!
- **Attributes** Store and evaluate and register commands and functions through MUSH-Like **attributes api**.
- **Grid** Build a grid in-game, load rooms or entire pre-built areas from flat JSON files or both!
- **Input Middleware**: The server allows for registering middleware to handle how in-game input.

### File Structure
Before we begin, we need to set up our basic file structure for the project.  First, I'll list all of the commands needed, then we'll break them down to examine what all of this is doing.

```
mkdir ursamu
cd ursamu

mkdir src
mkdir src/api
mkdir src/config

touch src/ursamu.ts
touch src/api/mu.ts
touch src/config/config.json
touch .gitignore

npm init -y
```

Then we create stubs for some files that we're going to be working with. Finally, we add our package.json with all of the defaults, which will do for now!  

### Dependencies
Next, we install the dependencies for our project.  We will install the base packages first, before adding our development only npm calls.
```
npm install express socket.io nedb @ts-stack/markdown shortid
npm install -D typescript @types/node @types/socket.io @types/express @types/nedb  @types/shortid

```
While we're working on development, we're going to want a way to reload the server automatically and save us a few keystrokes in the process.  For this, we're going to be using nodemon.  And, as it's a tool I use on MULTIPLE projects, I tend to install it globally.  On Linux, it means you need to elevate your permissions with sudo:
```
sudo npm install -g nodemon
```
And then set up our tsconfig.json file:
```
npx tsc -init

```

npx will call our local install of typescript, instead of installing typescript as a global command.

Our folder structure should now look something like this:

```
/ursamu
    /src
        /api
            - mu.ts
        /config
            - config.json
        - ursamu.ts
        - tsconfig.json
    - .gitignore
    - package.json
``` 
**Now!** We need to edit our `package.json`.  First, we'll add our start scripts:
```JavaScript

"scripts": {
    "prestart": "npx tsc -p ./src/tsconfig.json ./src/ursamu.ts", 
    "start": "node ./dist/ursamu.js",
    "start:watch": "nodemon"
  },

```

Finally, we need to add a nodemon config section to our package:
```JavaScript
"nodemonConfig": {
    "ignore": [
      "**/*.test.ts",
      "**/*.spec.ts",
      ".git",
      "node_modules"
    ],
    "watch": [
      "src"
    ],
    "exec": "npm start",
    "ext": "ts"
  }
```
And last, we're going to add our config file into `src/config/config.json`.
```JSON
{
  "game": {
    "port": 8090
  }
}

```
### That was a bit of setup and exposition! Now let's move onto some code!

Our first step is going to set the MU class, which will handle a lot of the socket.io book-keeping and game startup methods. Notice the `constructor` is private, and `instance` is a static property.  We're going to make the MU class into a Singleton.  I could probably just get away with exporting an instance of the class, but this way future me (or you!) doesn't try to instantiate the class again!

```JavaScript
import { EventEmitter } from "events";
import { Server, Socket } from "socket.io";
import { game } from "../config/config.json";

export class MU extends EventEmitter {
  io: Server | undefined;
  private static instance: MU;
  connMap: Map<string, DBObj>;

  private constructor() {
    super();
    this.io;
    this.connMap = new Map();
  }

  /**
   * Get an instance of the MU Class.
   */
  static getInstance() {
    if (!this.instance) {
      MU.instance = new MU();
    }

    return MU.instance;
  }
```
In order to keep track of our socket server from any other piece of the server we decide to call `mu.ts` from, it will have a stored instance of the server, using `mu.attach()`.
```JavaScript
 /**
   * Attach to a Socket.io  server implementation.
   * @param io The Socket.io server to attach too.
   */
  attach(io: Server) {
    this.io = io;
    return this;
  }
```
This is where we'll handle things like listeners for new data from
sockets, checking to make sure the starting room is built, etc.  A nice facade for implementation details! :)
```JavaScript
  /**
   * Start the game engine.
   * @param callback An optional function to execute when the
   * MU startup process ends
   */
  async start(callback?: () => void) {
    if (typeof callback === "function") callback();
  }
}
```
Then, I make a call to `getInstance()` for a new instance of the MU class, and share the object as the file's default export.  Now, whenever it's imported from another file it'll be working with the same instance, and can't create another.
```JavaScript
const mu = MU.getInstance();
export default mu;

```
Finally, we'll start our ursamu.ts main file, to see the code in action!
```JavaScript
import express, { Request, Response } from "express";
import { Server } from "http";
import socketio from "socket.io";
import config from "./config/config.json";
import { resolve } from "path";
import ursamu from "./api/mu";

// Define the various communication channels.
const app = express();
const server = new Server(app);
const io = socketio(server);
const mu = ursamu.attach(io);

app.use(express.static("public"));

app.get("/", (req: Request, res: Response) =>
  res.sendFile(resolve(__dirname, "../public/index.html"))
);

mu.start(() =>
  server.listen(config.game.port, () => {
    console.log(`Server started on port: ${config.game.port}`);
  })
);
```
That should about do it for part one! Next installment we'll setup `Socket.io` and the `parser`!  Feel free to **follow** to get updates on my articles and new posts!  Also, feel free to leave a comment or post a question! :)