Creating our first Node.js package with Typescript

Creating our first Node.js package with Typescript

In this article, we will learn how to create a Node.js package with Typescript and publish it to npmjs.com

Leonardo Cabeza's photo
Leonardo Cabeza
·Aug 31, 2022·

12 min read

Good to know before reading this:

What you will learn by reading this:

  • Export a typed function
  • Publish a Node.js library to npm

Why would you want to learn this?

  • Typescript usage has grown a lot over the last few years.
  • You want to share a library to friends, colleagues for everyone to use.
  • You want feedback on a library/project you have been working on a while.

We will create a module with a function whose sole purpose is to sum two numbers, just to keep it simple.

Initial setup

We’ll start by creating a new folder in our operating system of preference, since I’m on a mac, I’ll open the terminal and create a folder in my home folder called suma-dos (this name has to be unique, since it will be the package name in npmjs.com), I do this **with the mkdir command: mkdir suma-dos, **then I will cd into the folder with the cd command: cd suma-dos.

At this point, we have to initialize our npm project, we do that by writing this command in our terminal: npm init —yes, this only creates a package.json **file with some default values:

{
  "name": "suma-dos",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Now, we will start writing some code, we create a folder, let’s call it src, and add a file there named index.ts, so far our project looks like this: Untitled.png

Let’s update our src/index.ts file, and we are going to add a function, whose job is to sum two numbers, we write it like this:

const sumaDos = (arg1: number, arg2: number): number => {
  /* TODO: should check if both are numbers */
  return arg1 + arg2;
}

export {
  sumaDos
}

We can see our function is fully typed, we have two arguments which are numbers, and the return value is also a number, and then we use a named export.

Typescript configuration

Now, by the time I’m writing this, Node.js does not understand Typescript, so, we need a tool to translate typescript to javascript, we are going to use the Typescript CLI tool, we install it by typing in our project folder:

npm install typescript --save-dev

That will install the binary we need to transpile the code, now, we will create a script in our package.json file for that, so we now have in our package.json this content:

{
  "name": "suma-dos",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "typescript": "tsc"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript": "^4.7.4"
  }
}

Finally, to transpile our typescript file, we need a base config for Typescript binary to work on, and we do that by writing this command in our terminal:

npx tsc —init

This will create this file (tsconfig.json):

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Projects */
    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */

    /* Language and Environment */
    "target": "es2016",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
    // "experimentalDecorators": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */
    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */

    /* Modules */
    "module": "commonjs",                                /* Specify what module code is generated. */
    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
    // "moduleResolution": "node",                       /* Specify how TypeScript looks up a file from a given module specifier. */
    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
    // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
    // "resolveJsonModule": true,                        /* Enable importing .json files. */
    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

    /* JavaScript Support */
    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */

    /* Emit */
    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
    // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
    // "removeComments": true,                           /* Disable emitting comments. */
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
    // "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */
    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
    // "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */

    /* Interop Constraints */
    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */

    /* Type Checking */
    "strict": true,                                      /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */

    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  }
}

This might seem overwhelming at first… because it is, but we only need a couple of them, so wil replace that with our own:

{
  // Change this to match your project
  "include": ["src/**/*"],
    // We don't need to transpile these directories
  "exclude": ["node_modules", "dist"],
  "compilerOptions": {
    // Tells TypeScript to read JS files, as
    // normally they are ignored as source files
    "allowJs": false,
    // Generate d.ts files
    "declaration": true,
    // This compiler run should
    // only output d.ts files
    "emitDeclarationOnly": false,
    // Types should go into this directory.
    // Removing this would place the .d.ts files
    // next to the .js files
    "outDir": "dist",
    // go to js file when using IDE functions like
    // "Go to Definition" in VSCode
    "declarationMap": true,
    "strict": true,
    "esModuleInterop": true,
    "moduleResolution":"Node"
  },
}

If you want to know more about this file, you can always check the tsconfig.json docs.

Let’s now transpile our code, by running the typescript script, we run in our console the next command:

npm run typescript

If we did everything right, we should have a **dist** folder with this content: Untitled 1.png

dist/index.d.ts will have the types of our function: Untitled 2.png

dist/index.js will have the transpiled code, so Node.js can understand the code:

Untitled 3.png

Publishing our package to npmjs.com

First off, we need to update our package.json file, since npm use some fields from this file at the time of publishing:

  • name: this has to be an unique name, make sure it doesn’t exists by searching in npmsjs.com first.
  • version: this has to be unique in each deploy, since you can’t publish a package with the same version twice.
  • main: this is the entrypoint for our application, in our case, it should point to our transpiled javascript file.
  • files: we list additional files we want to add when we publish the package, in our case, we need to add the dist folder since that’s where our javascript file and types are.
  • types: here we define where our types are, so another tools using our package (npmjs.com, vscode, and others), know where to find them.

Our package.json should look like this at this point:

Untitled 4.png

Before publishing, we have to transpile and create/update typescript files to javascript, and also, create/update types, we can do that by running this command in our terminal:

npm run typescript

If we want to see what files we are going to publish, we can execute this command in our terminal:

npm publish --dry-run

And we should get something like this:

Untitled 5.png

We are almost there, we need a npmjs.com account, if you don’t have one, you can create it here.

If you do already have one, we have to authenticate the npm cli tool with npmjs.com:

npm login

We go through each of the steps, where we input our username, password, and email, and OTP if enabled on the account. We can then validate that we did everything right by executing a command:

npm whoami

And this should return our username, finally we can enter our last command to publish our Node.js library to npm:

npm publish

You should get something like this in your console:

Untitled 6.png

And something like this in https://www.npmjs.com/package/suma-dos:

Untitled 7.png

Notice the blue Typescript icon at the right of the library name, it means it’s detecting the types in our library.

Now we can immediately share our library with our colleagues and/or friends, we let them know the name of our library (suma-dos) and then they can install with:

npm install suma-dos

or

yarn add suma-dos

And finally to use our library in a Node.js project we will do it like this:

import { sumaDos } from ‘suma-dos’;

const result = sumaDos(1, 2);
console.log('result', result); // this would return 3

What did you learn?

If you managed to get this far, this a summary of what you learned:

  • Make a small typescript library in Node.js
  • You know how to type your exported functions
  • You know how to publish your library to npmjs.com

Voilà!

If you want to see unit tests and CI/CD github configuration to publish to npmjs.com on every git push, you can see the complete suma-dos repo here.

 
Share this