Published on

TypeScript has a terrible developer experience

Authors
  • avatar
    Name
    Jordan Stewart
    Twitter

I find the development process with TypeScript painful in every step of development due to divergent tooling. I find the TypeScript language good, but I find everything around the language bad. In comparsion, I find the Go language from easy in basically every aspect (Check my other most...).

You can experience this headache setting up a basic webserver, as you go through a lot of the steps in the development process. So here we go...

How fast can you deploy a server in TypeScript from scratch?

3... 2... 1... go!

1. Make a folder:

mkdir typescript-deployment-minimal

2. initiate npm

npm init -y

This creates a lot of values I don't care for like version, description, main, author and license.

3. initiate typescript

npm install typescript @types/node --save-dev
tsc --init # create tsconfig.json

This creates the tsconfig.json, required for Typescript.

4. get stuck looking at tsconfig.json

The tsconfig.json file generated from tsc --init is 109 lines long, and has a doesn't have sane defaults...

This is a sane default to have though (I had to look this up on youtube, the documentation isn't great):

{
  "compilerOptions": {
    "target": "esNext",
    "module": "NodeNext",
    "rootDir": "./src",
    "allowJs": false,
    "sourceMap": true,
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

It's got a src for code and dist folder for compiled code (or javascript). You probably don't need "sourceMap": true,

5. write a web server in src

Let's use fastify as the web server as people would laugh at you if you just use the http module, and express.js is legacy now (no http/2).

npm install fastify

And let's hit chatgpt to quickly get some code:

import fastify from 'fastify';

const server = fastify({
    logger: true
});

server.get('/', async (request, reply) => {
    return { hello: 'world' };
});

const start = async () => {
    try {
        await server.listen({ port: 3000 });
    } catch (err) {
        server.log.error(err);
        process.exit(1);
    }
};

start();

This is great. It's short and we have a logger too. Otherwise we would have had to have installed and configured a logger like winston.

I wanted to protect you from this, but here is the type of the fastify server:

declare function fastify<
  Server extends http.Server,
  Request extends RawRequestDefaultExpression<Server> = RawRequestDefaultExpression<Server>,
  Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
  Logger extends FastifyBaseLogger = FastifyBaseLogger,
  TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
>(opts?: fastify.FastifyHttpOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & PromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>

To get the port of the server it's:

        const address = server.server.address() as AddressInfo
        address.port

I have a lost a lot of time to complicated types like these.

compile and run

compile the code with:

tsc

With bigger projects you probably want to move to swc, or the speedy web compile for builds as compiling is a lot faster. There is esbuild aswell.

run the code with:

node dist/index.js

Sweet...

With more advanced projects you probably want to compile with swc the speedy web compiler, or esbuild. I think they are a lot faster they tsc or typescript compile.

As an aside to run Typescript code with bun, it is just:

bun run index.ts

You are generally stepping through two tools to run and compile.

deploy it - it's not done until it is running in production

setup the server

Let's create a tiny EC2 instance on amazon web services (AWS), like t4g.nano spot instance. We will just use ssh, and scp to setup the server.

Let's connect to the server

ssh -i ~/.ssh/ec2-key.pem ec2-user@52.64.95.107

Once on the server we need to install node. You would think it is just:

sudo yum install node

But that doesn't work.

It's actually:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash # install node version manager
source ~/.bashrc # load nvm
nvm install --lts # install long term support version

Let's make a directory for the code as well on the server:

mkdir node

move the code to the server, and run the code

Now the server is setup let's move the code there.

# copy the package.json file for dependencies
scp -i ~/.ssh/ec2-key.pem package.json ec2-user@52.64.95.107:/home/ec2-user/node
# copy the package-lock.json for dependency versions
scp -i ~/.ssh/ec2-key.pem package-lock.json ec2-user@52.64.95.107:/home/ec2-user/node
# copy the dist directory for the compiled code
scp -i ~/.ssh/ec2-key.pem -r dist/ ec2-user@52.64.95.107:/home/ec2-user/node

Back on the server we need to re-install the packages, and run the code:

cd node
npm install --production # you could copy the node_modules, but i think installing is more safer
# the production flag seem you don't install devDependencies
# now let's run the server:
nohup node dist/index.js > application.log &

nohup mean no hang up, it prevents the linux server from shutting now the process. & runs the process in the background.

thoughts on developer experience

And that's it, we have a server listening on port 3000, serving content.

I can deploy a web server somewhat quickly, but I felt pain on basically every step:

  • The package.json file has a lot of garbage in it, which annoys me
  • I get overwhelmed by the tsconfig.json file being 100 lines of options from tsc --init
  • The types of some objects are crazy.
  • Compiling can run into issues with misconfigured tsconfig.json, and with bigger projects you would want to move to the speedy web compiler swc for a lot more speed
  • on the server I needed to install node, a node version manager and the libraries again, and scp a directory
  • writing a test you need another tool (see below)

We also missed out on setting up prettier, and eslint, and sharing code between projects, and workspaces, which can be very confusing.

I did enjoy the good library support though.

Check my other post on how this compares to Go language.

bonus... setting up testing

Here is a bonus of setting up tests with node. You can use jest, or vitest. I'm using vitest as it's more modern.

npm install -D vitest

You generally need vitest.config.ts file, and to add the test script to package.json.

From there you write tests in the file like .test.ts.

References:

  1. How to install node on amazon linux: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html