Backend | Part 1 — Deploying a Node.js Backend with TypeScript

Setup

npm init -y

touch index.js

In package.json set "type": "module" to enable ES6 modules.

{
  "name": "backend",
  "main": "index.js",
  "scripts": {},
  "author": "Josh Holloway",
  "type": "module"
}

Libs

npm i -D nodemon

npm i express cors dotenv

Add scripts:

{
  "name": "backend",
  "main": "index.js",
  "author": "Josh Holloway",
  "type": "module",
  "devDependencies": {
    "nodemon": "^3.0.1"
  },
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.18.2"
  },
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js"
  }
}

Env vars

Note that we don't yet have an enviroment variable set for NODE_ENV. Logging process.env.NODE_ENV will result in undefined.

console.log('NODE_ENV: ', process.env.NODE_ENV);

Create a .env file:

touch .env

NODE_ENV=development
PORT=5000

Then, in index.js:

import * as dotenv from 'dotenv'
dotenv.config()
console.log('NODE_ENV: ', process.env.NODE_ENV);

Local / remote repo

Create .gitignore:

touch .gitignore
.env
.DS_store
node_modules

Create local repo and commit to remote repo:

git init

git branch -M main

git status

ls -lah

git add .

git commit -m 

git remote add origin https://github.com/joshdotjs/backend

git push -u origin main

Determine Node.js version and set in engines property in package.json.

node -v
npm -v

Add this to package.json:

"engines": {
  "node": "18.15.0",
  "npm": "9.5.0"
},

TypeScript

Add TypeScript and rimraf:

npm i -D typescript rimraf @types/node @types/express @types/cors

Add a build script in package.json.

"scripts": {
  "start": "node dst/index.js",
  "dev": "tsc && nodemon dst/index.js",
  "build": "rimraf && tsc"
}

We are using rimraf to clear out our /dst folder on each build.

Create a /src directory and place index.ts inside.

Create a tsconfig.json file:

touch tsconfig.json

{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "target": "ES2022",
    "sourceMap": true,
    "outDir": "dst",
  },
  "include": ["src/**/*"],
}
  • "module": "NodeNext": This option specifies the module code generation method. The value "NodeNext" refers to the module resolution strategy optimized for Node.js with ECMAScript modules (as of a newer Node.js version that supports this).
  • "moduleResolution": "NodeNext": This option sets the module resolution strategy. Setting it to "NodeNext" makes the compiler use a resolution strategy tailored to Node.js with ECMAScript modules.
  • "target": "ES2022": This option tells the TypeScript compiler to transpile the code to ECMAScript 2022 syntax. This means that the generated code will use features available in the ES2022 standard.
  • "sourceMap": true: This flag enables the generation of source maps. Source maps help in debugging by mapping the compiled JavaScript back to the original TypeScript source code.
  • "outDir": "dst": This option specifies the directory where the compiled JavaScript files will be emitted. In this case, the output directory is named "dst."
  • "include": ["src/**/*"]: This option specifies a glob pattern for files that should be included in the compilation. Here, it includes all files within the "src" directory and its subdirectories.

ES Modules

Make sure to import files local to the project with their file extension.

  • Imports from node_modules do not need a file extension.
  • Use .cjs for compiled .cts TypeScript files that contain CommonJS module syntax.
  • Use .js for compiled .ts TypeScript files that contain ES6 module syntax.
import x1 from './file.js';
import x2 from './file.cjs';
console.log(x1);
console.log(x2);

import express from 'express';

file.ts:

export default "file.js";

file.cts:

module.exports = "file.cjs";

Set up an endpoint and add a fake DB via fs

Let's create a temporary fake database utilizing the file system in order to test our setup.

touch count.txt

Inside /count.txt write a single character of 1. Our test code will read this number, increase its value by one, display this value, and update this file stored on the server with the new incremented value.

1

Below is our server test code inside/src/index.ts.

import x1 from './file.js';
import x2 from './file.cjs';
console.log(x1);
console.log(x2);


import * as dotenv from 'dotenv';
dotenv.config()
console.log('NODE_ENV: ', process.env.NODE_ENV);

// const express = require('express');
import express, { Express, Request, Response } from 'express';

// const cors = require('cors');
import cors from 'cors';

import { readFileSync, writeFileSync } from 'fs';

// ==============================================

const server = express();

// middleware
server.use(express.json());
server.use(cors());

// ==============================================

server.use('/home', (req: Request, res: Response) => {

  const count = readFileSync('count.txt', 'utf-8');
  console.log('count: ', count);

  const new_count = parseInt(count) + 1;

  writeFileSync('count.txt', new_count.toString());

  const html = `
    <htnl>
      <head></head>
      <body style="height: 100vh; display: grid; place-items: center;">
        <h1>Success with TypeScript!</h1>
        <h5>This HTML is the response of a GET request to our deployed Node.js endpoint <code>/home</code>.</h5>
        <p>count: ${new_count}</p>
      </body>
    </html>
  `;

  res.send(html);
});

// ==============================================

server.use('*', (req: Request, res: Response) => {
  const html = `
    <htnl>
      <head></head>
      <body>
        <h1>404 - catch all endpoint</h1>
      </body>
    </html>
  `;
  res.send(html);
});

// ==============================================

const PORT = process.env.PORT ?? 5000;
server.listen(PORT, () => {
  console.log(`listening on port: ${PORT}`);
});

Code + Demo

Conclusion

In this post, we set up a Node.js backend utilizing TypeScript. We'll use this base project as the starting point for our custom backend that will mainly be used as a REST API. We'll eventually add bcrypt for authentication and a payment system utilizing the Stripe API. In the next post, we'll add in a real Postgres database. Stay tuned 📺.