With new releases and tools, setting up a node server has become super simple and until NodeJs ships with typescript built-in, adding typescript is an essential need.
I will show you the SIMPLEST setup you can have to kick off your next node project confidently. For simplicity, you can customize it with the things you need to complete your project.
Project Directory Setup
Let's start by setting you your project directory
mkdir YOUR_PROJECT_NAME
cd YOUR_PROJECT_NAME
git init # start your git project
npm init -y # initialize npm with defaults
mkdir src # create the source directory for all the code
Setting up Typescript
Let's start with a couple of installs…
npm install -D typescript @types/node
The @types/node
pretty much sets up types for the entire Node itself.
We can now create the ts-config.json
file by running:
npx tsc --init
This will add a ts-config.json
file to the root of the project with all defaults including commented-out ones. We need to have the following configurations
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"rootDir": "./",
"resolveJsonModule": true, /* in case you are importing JSON files */
"outDir": "./build",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true,
}
}
Feel free to uncomment a few other options to fit your project. This should be good to start.
Create a simple server
To test if everything is working fine, let's add a simple server code.
Create a file src/index.ts
and add the following code:
// src/index.ts
import http from "http";
export const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
data: "It Works!",
})
);
});
server.listen(3000, () => {
console.log("Server running on http://localhost:3000/");
});
This is a simple HTTP Node server listening on port 3000. It should be enough to test our setup.
Now let's try to build this by adding a build script to our package.json file.
"scripts": {
"build": "tsc"
}
Now if you run npm run build
you should see a build directory added to the project root folder with a src
directory and the package.json
file. The server code should look something like so:
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.server = void 0;
const http_1 = __importDefault(require("http"));
exports.server = http_1.default.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
data: "It Works!",
}));
});
exports.server.listen(3000, () => {
console.log("Server running on http://localhost:3000/");
});
This confirms our build works!
Set up how to run the server
We have to think about it in terms of the final build. Because we know the final build will be in the build directory and it will be in javascript, we can set the start script like so:
"scripts": {
"build": "tsc",
"start": "node src"
}
The start will not work if we try to run it from our root directory because our source code is in typescript. For that, we need to do a couple more setups.
npm install -D nodemon rimraf npm-run-all ts-node
With those installed let's improve our build by adding a clean
script and make the build
script clean before the new build.
"scripts": {
"clean": "rimraf ./build",
"build": "npm run clean && tsc",
"start": "node src",
}
With that, let's add the scripts to run the project locally:
"scripts": {
"clean": "rimraf ./build",
"build": "npm run clean && tsc",
"start": "node src",
"local": "ts-node src",
"local:watch": "nodemon src -e ts,json --exec 'npm run local'",
}
The local script uses ts-node
to run our project instead of node
command. This is pretty much a node but in typescript.
But local
command only runs the server once and will not detect your changes. That's why the local:watch
is needed so we can run the server and watch for changes when you are working.
It uses nodemon
to run the project in src
directory watching for extensions (-e
) ts
and json
so whenever there are changes to execute ( --exec
) the npm run local
script command.
When developing, simply run npm run local:watch
.
Note: I am using a JSON Viewer extension in Chrome
Test Setup
I do not recommend going without a test. There have been great things happening on the testing side of things with the release of a built-in test runner in Node which will make things simpler.
However, I still use jest and the following setup is easy to swap to something else if you prefer.
Start with the following installs:
npm i -D supertest @types/supertest jest @types/jest ts-jest
We will be using supertest
to test our server and the rest is just jest
, its types ( @types/jest
) and a jest typescript version ( ts-jest
) to run things.
Now you can add the jest.config.js
file to the project root folder with the following code:
module.exports = {
transform: {
'^.+\\.ts?$': 'ts-jest',
},
testEnvironment: 'node',
testRegex: './src/.*\\.(test|spec)?\\.(js|ts)$',
moduleFileExtensions: ['ts', 'js', 'json'],
roots: ['<rootDir>/src'],
};
The setup is super simple, feel free to add more extensions as you need but overall, that's all you need to start with jest and typescript.
With that, we can add the test script:
"scripts": {
"clean": "rimraf ./build",
"build": "npm run clean && tsc",
"start": "node src",
"local": "ts-node src",
"local:watch": "nodemon src -e ts,json --exec 'npm run local'",
"test": "jest"
}
Now let's test our server by adding the index.test.ts
file inside the src directory with the following code:
import supertest from "supertest";
import { server } from "./index";
describe("Server", function () {
const request = supertest.agent(server);
afterAll((done) => {
server.close(done);
});
it("should get /", async () => {
const res = await request.get("/");
expect(res.status).toBe(200)
expect(res.body).toEqual({"data": "It Works!"})
});
});
This is simply checking if when we make a GET
request to our basic server, we get what we set it to return.
One thing we need to do now is to exclude test files ( *.test.ts
) from our build by adding them to the exclude option in the ts-config.json
file.
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"rootDir": "./",
"resolveJsonModule": true, /* in case you are importing JSON files */
"outDir": "./build",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true,
},
"include": [
"src",
"package.json"
],
"exclude": [
"src/**/*.test.ts"
]
}
We also want to include the package.json
so the npm start
can work when we deploy the build directory somewhere like Netlify, AWS ElasticBeanStalk, Heroku, etc.
Use the exclude and include list to add or remove final production artifacts.
ESLint and Prettier Setup
Linting and auto-formatting our code makes it consistent and easier to read. It also catches things we often don't pay attention to by improving the quality of our code even more.
Start by installing few things:
npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint prettier eslint-config-prettier
Now create the .eslintrc
file with the following code:
{
"extends": [
"eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"
],
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"root": true
}
We can now create the lint
, format
and format:check
scripts like so:
"scripts": {
"clean": "rimraf ./build",
"build": "npm-run-all lint format clean && tsc",
"start": "node src",
"local": "ts-node src",
"local:watch": "nodemon src -e ts,json --exec 'npm run local'",
"lint": "eslint src",
"format": "npx prettier --write src",
"format:check": "npx prettier --check src",
"test": "jest"
}
Note that the build
script was changed to run the lint
script before the cleaning and build.
You should configure your IDE or code editor to use the eslint configuration to auto-check and format things when you make code saves to get the best of this setup. Otherwise, manually run these commands on your own.
This is how it looks for JetBrains IDEs like IntelliJ and WebStorm:
Final touches
We need the .gitignore
file to make sure we don't commit things we don't have to. It should look like this
# IntelliJ project files
.idea
*.iml
out
gen
# VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# Folders
dist
build
node_modules
Take Away
This setup is all you need to start. Depending on your project you may need additional things.
Check all the code in the following template repo. Let me know if you have any questions or need further assistance with something else.
YouTube Channel: Before Semicolon
Website: beforesemicolon.com
Top comments (0)