What if I told you, you could learn to program by playing with spaceships? Well you can, and in this post I’ll demonstrate how to get started quickly with SpaceTraders API and typescript.

SpaceTraders API

SpaceTraders API is an

API-based game where you acquire and manage a fleet of ships to explore, trade, and fight your way across the galaxy. Use any programming language with [their] API to control the most powerful fleet in universe.

Some years back I used v1 of the api to learn XState and progressed further to build out a full UI and experiment with XState Actors. Well v2 of the api has been out for over a year and it could be time to automate some spaceships again, so let’s get started!

Initialisation

I’m going to use typescript as my preferred language for this project and I’ll use the openapi-generator-cli to generate the api client from the SpaceTraders API OpenAPI spec.

# create project folder
mkdir my-spacetraders-project
cd my-spacetraders-project

# generate api client, needs java :(
npx @openapitools/openapi-generator-cli generate -g typescript-axios -o api \
  -i "https://stoplight.io/api/v1/projects/spacetraders/spacetraders/nodes/reference/SpaceTraders.json?fromExportButton=true&snapshotType=http_service&deref=optimizedBundle"

# initialise node & dependencies
npm i axios typescript ts-node-dev

# initialise typescript
node_modules/.bin/tsc --init

Implementation

With the above done, we’re ready to start programming spaceships. Creating a new file, index.ts, we can put our first API call in place.

import { DefaultApiFactory } from "./api";

(async () => {
    while (true) {
        const result = await DefaultApiFactory().getStatus();
        console.log(result.data);
        await new Promise(r => setTimeout(r, 300_000));
    }
})();

To run the above we’ll add a script to package.json:

  "scripts": {
    "start": "ts-node-dev ./index.ts"
  },

And now we can kick it off by running npm start:

$ npm start

> start
> ts-node-dev ./index.ts

[INFO] 18:03:39 ts-node-dev ver. 2.0.0 (using ts-node ver. 10.9.2, typescript ver. 5.4.2)
{
  status: 'SpaceTraders is currently online and available to play',
  version: 'v2.1.5',
  resetDate: '2024-02-25',
  description: 'SpaceTraders is a headless API and fleet-management game where players can work together or against each other to trade, explore, expand, and conquer in a dynamic and growing universe. Build your own UI, write automated scripts, or just play the game from the comfort of your terminal. The game is currently in alpha and is under active development.',
  stats: { agents: 1276, ships: 4036, systems: 8498, waypoints: 172078 },
  ...

Congrats! If you got this far, you’re primed and ready to start programming spaceships. Check out the API docs and get programming. For a pleasant developer experience, the script we added restarts every time we make a change to the code, so you can iterate quickly.

But could it be better? If you’re happy and just getting started, then read no further - grumpy programmers with experience, read on, because of course it could be better.

Top-level Await

I find the Immediately Invoked Function Expression (IIFE) annoying - it’s ugly and confusing to beginners but dropping it for top-level await takes further effort. I’ve failed to get it working with ts-node so we’ll swap it for tsx:

npm uninstall ts-node-dev
npm i tsx

Update the default tsconfig.json with the following changes:

    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "Bundler",

And make these changes to package.json:

  "scripts": {
    "start": "tsx watch ./index.ts"
  },
  "type": "module",

And our code much slicker:

import { DefaultApiFactory } from './api'

while (true) {
  const result = await DefaultApiFactory().getStatus()
  console.log(result.data)
  await new Promise((r) => setTimeout(r, 300_000))
}

Production-grade

So there’s a bunch of other things experienced developers expect, including formatting, linting, testing and building. I do want those features because they make development life easier, but I don’t want to spend time setting them up. Thankfully I don’t have to becuase ts-toolkit will do it for me. Let’s drop our tsconfig (the toolkit will give us a new one), move our source into src and generate all those niceities:

rm ./tsconfig.json
npx @makerx/ts-toolkit init
mkdir src
mv ./index.ts ./src/index.ts

We should update our start script in package.json to the new location too:

    "start": "tsx watch ./src/index.ts"

I’m in vscode, and wow look at all those squiggles in index.ts. Let’s auto-format on save by adding a .vscode/settings.json:

{
    "editor.formatOnSave": true
}

We can save to auto-fix some of the squiggles in index.ts, and for those lint rules we don’t care about we can disable in the file (shown) or in generated config:

import { DefaultApiFactory } from '../api'

// eslint-disable-next-line no-constant-condition
while (true) {
  const result = await DefaultApiFactory().getStatus()
  // eslint-disable-next-line no-console
  console.log(result.data)
  await new Promise((r) => setTimeout(r, 300_000))
}

package.json now has a bunch of different scripts that you’re probably familiar with. Our build artifact is produced by npm run build. Note that the toolkit builds both commonjs and esm modules and top-level await is not supported in commonjs, which we can solve by removing the cjs config from rollup.config.js.

Now we can deploy our artifact anywhere and run it with node ./dist/src/index.mjs

Source

If all of this was too much and you want it even easier, then feel free to clone the source

git clone git@github.com:staff0rd/spacetraders-getting-started.git
cd spacetraders-getting-started
npm i
npm start

Enjoy!