DEV Community

Alexandru Ene
Alexandru Ene

Posted on

Setting Up Node.js + TypeScript for Deployment

Just a word before

Hey, guys! I didn't post anything in a while. I focused on a project and it was hard for me to keep up with everything. The good news is that I got into its final stage, kind of. And as soon as it is ready, I will share it here with you. Because I will really need your feedback, good or bad.

I guess I couldn't keep my word and post every week. I am truly sorry. So now that I am here, I think this is a good moment for me to remind myself to push some more. That's how we grow, right?


Introduction

Whenever I start a project and have to deal with Node and TypeScript, I always forget stuff about tsconfig.json, the scripts to include in package.json, what types to include, why on earth I get 1 million of Cannot find module and Cannot use import statement errors, why do TypeScript shout that much about types and so on.

In a nutshell, I can't remember the setup very well. And I always feel disappointed in me, like can't you just remember for once everything, but like everything, in this world? Damn...


Overview

  1. Initialization
  2. tsconfig.json and package.json
  3. Scripts
  4. Dependencies
  5. Render Tips
  6. Conclusion

1. Initialization

This should be easy, like very. Unless your brain decides it's not, so why not forget everything? So let's keep this in mind:

  • We initialize Node.js, which will create a package.json file. We'll edit this later to suit our needs.
npm init -y
Enter fullscreen mode Exit fullscreen mode
  • We setup TypeScript: we'll have our nice tsconfig.json file, which we should be very careful about later on.
npx tsc --init
Enter fullscreen mode Exit fullscreen mode
  • We keep our folder structure clear
/project-root
  /src
    index.ts
  /dist
  package.json
  tsconfig.json
  .env
  .gitignore
Enter fullscreen mode Exit fullscreen mode

src/: our code goes here

dist/: compiled JavaScript code after running tsc

.env: for environment variables which we never commit

.gitignore: brain, remember to ignore node_modules, .env, and dist/


2. tsconfig.json and package.json

  • My basic modern TypeScript config is this:
{
  "extends": "@tsconfig/node22/tsconfig.json",
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src",
    "target": "ES2022",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true
  },
  "include": ["src"],
  "exclude": ["dist", "node_modules"]
}
Enter fullscreen mode Exit fullscreen mode

Why? It is as simple and easy to remember as possible. And it works. But, if I am here telling you this, it means I can't still remember every line. I sometimes look at them and wonder: what is your purpose? So let's quickly recap...

"extends": "@tsconfig/node22/tsconfig.json": a preset TypeScript configuration designed for Node.js targeting ES2022 that follows best practices, saves time and gives a smaller tsconfig file and up-to-date benefit. You can install it wiht this command: npm install -D @tsconfig/node22.

outDir: where the compiled JS goes - usually dist/

rootDir: where your source TypeScript files live - usually src/

target: the JS version to compile to, we use ES2022 to tell TypeScript to compile the code to JS that uses features supported in ES2022

module: what kind of JavaScript module syntax TS outputs (CommonJS, ESM), it enables the use of import/export syntax (ES Modules) instead of require and tells TypeScript to emit native ESM JavaScript instead of CommonJS (which uses require)

moduleResolution: how TypeScript finds and resolves imported modules/files, it should match module

strict: enables all strict type checks

esModuleInterop: allows default imports to work, even if those modules don't actually have a default export (like import express from 'express')

forceConsistentCasingInFileNames: all file and folder names in import paths should now match the actual casing of the files and folders, so we don’t accidentally import ./UserService instead of ./userservice

skipLibCheck: skips type-checking of node_modules and other libraries, to speed up builds and reduce type errors by not checking their .d.ts files

  • Now, since we’re using modern ES Modules ("module": "nodenext" in tsconfig.json), we also need to tell Node.js to treat our files as ESM - otherwise, even if you use import/export, Node will throw errors or expect require(). To do that, we add this line inside our package.json file:
"type": "module"
Enter fullscreen mode Exit fullscreen mode

3. Scripts

Now it's time to define the scripts we'll use to build, run, and develop our app. Again, brain, remember this, in our package.json file we should have:

"scripts": {
  "dev": "concurrently \"tsc --watch\" \"nodemon\"",
  "build": "tsc",
  "start": "node dist/index.js"
}
Enter fullscreen mode Exit fullscreen mode
  • "dev": "concurrently \"tsc --watch\" \"nodemon\"": When we run npm run dev, tsc --watch tells TypeScript to watch our src/ folder and automatically recompile .ts files into .js in the dist/ folder whenever we make changes. Nodemon watches our compiled files in dist/ and restarts the server when they change. So, we write TypeScript, it compiles and nodemon restarts the app. Great choice if you want to develop exactly the way your app builds.

  • "build": "tsc": Runs TypeScript compiler once, outputting all .js files from src/ folder into the dist/ folder. We should use this before deploying our app, especially on platforms like Render, which expects npm run build.

  • "start": "node dist/index.js": Starts the app by running the compiled JavaScript in the dist/ folder.


4. Dependencies

Setting up a Node + TypeScript project means installing both the libraries we use and their type definitions.

Sometimes I get stupid errors because I miss the libraries. For me, for now, these are the basics:

npm install express dotenv cors jsonwebtoken bcryptjs cookie-parser mongoose zod multer
Enter fullscreen mode Exit fullscreen mode

But... TypeScript doesn’t know how to type most libraries by default, so we install in dev dependencies their @types/* versions:

npm install -D @types/express @types/node @types/cors @types/jsonwebtoken @types/cookie-parser @types/multer @tsconfig/node22 concurrently nodemon
Enter fullscreen mode Exit fullscreen mode

We always include @types/node for basic Node types and the preset TS configuration we talked at point 2, @tsconfig/node22.

We also need this nodemon.json file for nodemon to work:

{
  "watch": ["dist"],
  "ext": ".js",
  "exec": "node dist/index.js",
}
Enter fullscreen mode Exit fullscreen mode

5. Render Tips

I had some problems when trying to deploy my backend. It was my first time, so I missed a lot of things. I tried Heroku, but then found Render and sticked with it.

These are the steps so Render will build our app corectly and deploy for the world to see:

  • We must first push our project on git
  • For an Express basic server, when using Render we go to Dashboard, then we click Add New and select Web Service and then we select the git repository
  • We set the build command in Render: npm install && npm run build. Render needs first to have all the dependencies. Only after it can build correctly.
  • We set the start command in Render, which is: npm start.
  • We make sure .env variables are configured in Render before trying to deploy it. I noticed once you deploy, you can't modify the env in their dashboard. I got real crazy trying to find those settings and failed miserably. Damn... If someone knows something, just let me know in the comments.

6. Conclusion

Now I hope that after writing this I won't forget anymore how to configure my server to be deployable without any errors... And I hope this is true for you too.

Joke aside, setting up Node.js with TypeScript can feel overwhelming at first. Especially because you handle more things at once. But once you break it down step by step, it’s actually not that painful. That's always the key in understanding and doing it properly. Break it until it's not working anymore. Kidding, I mean just break it into more manageable pieces. Take more steps to accomplish a task and you will succeed.

And don't be like me, before trying to deploy, make sure you actually have a index.ts file in your src folder! Silly me!

If you find it helpful, join the comments section and say what you have on your mind. What are your tips? I'd love to hear it!

Thank you for reading and see you in the next one!

Top comments (0)