Creating our first Node.js package with Typescript

Leonardo Cabeza
Published:
February 27, 2023
Updated
May 17, 2023
10
minute read
Typescript plus Node.js

In this article:

Node.js packages are modules of reusable code that can be easily shared and integrated into other projects. These packages can be published and downloaded from the Node Package Manager (npm).

TypeScript is a statically typed superset of JavaScript that adds additional features and structure to the language. It provides type checking, object-oriented programming concepts, and additional tooling to improve code quality and development time.

Combining Node.js packages with TypeScript can help simplify code management and increase productivity. In this article, we will discuss the process of creating a Node.js package with TypeScript and explore the advantages of using this approach.

Building packages in Node.js is important for several reasons:

Reusability:

By creating packages, developers can write code once and reuse it across multiple projects. This saves time and effort in the long run and reduces the amount of duplicated code.

Modularity:

Packages are modular units of code that can be easily updated, modified, or replaced without affecting other parts of the application. This makes it easier to maintain and scale applications over time.

Dependency Management:

Node.js packages come with a package.json file that lists all the dependencies required for the package to function properly. This makes it easier to manage dependencies and ensure that the package runs consistently across different environments.

Collaboration:

Packages can be published to npm, a package repository that allows developers to share their packages with the wider community. This fosters collaboration and allows developers to receive feedback and improve their packages.

Productivity:

By using packages, developers can focus on writing code that is specific to their project instead of writing code that has already been written before. This can help speed up development and increase productivity.

Overall, building packages in Node.js helps simplify code management, increase productivity, and encourages collaboration within the community. It also helps ensure code consistency and maintainability over time.

Good to know before starting to create your Node.js:

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:

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.

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 ''s from expanding the number of files TypeScript should add to a project. */

(continuation...)


    /* 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 we will 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
dist/index.d.ts will have the types of our function
dist/index.js will have the transpiled code, so Node.js can understand the code

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:

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:


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:

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

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.

FAQs

What are Node.js packages and how can they be used?

Node.js packages are modules of reusable code that can be easily shared and integrated into other projects. Developers can create packages to write code once and reuse it across multiple projects, saving time and effort. These packages can be published and downloaded from the Node Package Manager (npm), allowing for easy sharing and collaboration within the developer community.

What is TypeScript and why is it useful in combination with Node.js packages?

TypeScript is a statically typed superset of JavaScript that adds additional features and structure to the language. It provides type checking, object-oriented programming concepts, and additional tooling to improve code quality and development time. When combined with Node.js packages, TypeScript can help simplify code management, increase productivity, and enhance code consistency and maintainability over time.

Why is building packages in Node.js important?

Building packages in Node.js offers several benefits. They allow developers to write code once and reuse it across multiple projects, reducing duplication and saving time and effort. Packages are modular units of code that can be easily updated, modified, or replaced without affecting other parts of the application, making it easier to maintain and scale applications over time. Node.js packages also simplify dependency management and encourage collaboration within the developer community, fostering feedback and improvement. Finally, leveraging packages can increase productivity by allowing developers to focus on project-specific code rather than reinventing existing solutions.

What are the steps involved in creating a Node.js package with TypeScript?

The process of creating a Node.js package with TypeScript involves several steps. These include setting up the initial project structure and installing dependencies, writing TypeScript code with proper typing, transpiling the TypeScript code to JavaScript, configuring the package.json file, generating necessary files for publishing, authenticating with npmjs.com, and finally publishing the package to the npm registry.

How can I publish my Node.js package to npmjs.com?

To publish your Node.js package to npmjs.com, you need to perform several steps. First, update the package.json file with the required fields such as package name, version, main entry point, and additional files. Then, transpile the TypeScript code to JavaScript using the TypeScript CLI tool. Authenticate the npm CLI tool with your npmjs.com account. Finally, use the npm CLI tool to publish the package to npmjs.com, ensuring that it meets the necessary specifications. Once published, your package will be available on npmjs.com for others to install and use.

Build with us.
All Rights Reserved © Moonshot Partners 2022
Home
Services
Blog
Contact
More
Home
About Us
Services
Case Studies
Blog
Contact Us
Close Menu