Home Blog Page 33

Building a REST API with Express and TypeScript

0
typscript course
typscript course

Table of Contents

  • Introduction
  • Prerequisites
  • Step 1: Initialize a New Node.js Project
  • Step 2: Install Dependencies
  • Step 3: Setting Up TypeScript
  • Step 4: Setting Up Express
  • Step 5: Create Routes and Handlers
  • Step 6: Create Models and Controllers
  • Step 7: Connect to a Database (Optional)
  • Step 8: Set Up TypeScript Compilation
  • Step 9: Run the API
  • Conclusion

Introduction

Express.js is one of the most popular web frameworks for Node.js, and TypeScript offers static typing, which can improve the development experience and reduce errors. Combining Express with TypeScript allows developers to write maintainable, scalable, and type-safe applications.

In this guide, weโ€™ll walk through building a simple REST API using Express and TypeScript. By the end, you will have a fully working API with basic CRUD (Create, Read, Update, Delete) operations.


Prerequisites

Ensure you have the following tools installed on your system:

  1. Node.js: You’ll need Node.js to run the backend application.
  2. npm: The Node Package Manager will be used to install dependencies.

To check if Node.js and npm are installed, run:

node -v
npm -v

If these tools are not installed, download and install Node.js from here.


Step 1: Initialize a New Node.js Project

Start by creating a new directory for your project and initializing it as a Node.js project:

mkdir express-ts-api
cd express-ts-api
npm init -y

This will generate a package.json file with default settings.


Step 2: Install Dependencies

Now, install the required dependencies for Express and TypeScript:

npm install express
npm install typescript @types/express @types/node --save-dev
  • express: The Express web framework.
  • typescript: TypeScript compiler.
  • @types/express: Type definitions for Express.
  • @types/node: Type definitions for Node.js (useful for Nodeโ€™s built-in modules like fs, http, etc.).

Additionally, you can install ts-node and nodemon to run and watch TypeScript files in development:

npm install ts-node nodemon --save-dev
  • ts-node: Allows direct execution of TypeScript code.
  • nodemon: Automatically restarts the server when code changes.

Step 3: Setting Up TypeScript

Next, initialize TypeScript in your project by generating the tsconfig.json file:

npx tsc --init

The tsconfig.json file will be created in the root directory. Modify it for a Node.js and Express setup:

{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}

This configuration ensures that TypeScript compiles to modern JavaScript, targets ES6, and puts the output in the dist directory.


Step 4: Setting Up Express

Create the directory structure for your project:

mkdir src
touch src/index.ts

In src/index.ts, set up the basic Express application:

import express, { Request, Response } from 'express';

const app = express();
const port = 3000;

app.use(express.json()); // Middleware to parse JSON bodies

// Basic route
app.get('/', (req: Request, res: Response) => {
res.send('Hello, TypeScript and Express!');
});

// Start the server
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});

This code creates a simple Express app that responds to requests at the root (/) endpoint with a “Hello” message.


Step 5: Create Routes and Handlers

Letโ€™s set up basic routes for the API. Create a routes folder in the src directory:

mkdir src/routes
touch src/routes/userRoutes.ts

In userRoutes.ts, define routes for a simple CRUD API:

import express, { Request, Response } from 'express';

const router = express.Router();

// Sample in-memory store for users
let users: { id: number, name: string }[] = [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' }
];

// GET /users - Get all users
router.get('/users', (req: Request, res: Response) => {
res.json(users);
});

// GET /users/:id - Get a single user by ID
router.get('/users/:id', (req: Request, res: Response) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (user) {
res.json(user);
} else {
res.status(404).send('User not found');
}
});

// POST /users - Create a new user
router.post('/users', (req: Request, res: Response) => {
const newUser = {
id: users.length + 1,
name: req.body.name
};
users.push(newUser);
res.status(201).json(newUser);
});

// PUT /users/:id - Update a user by ID
router.put('/users/:id', (req: Request, res: Response) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (user) {
user.name = req.body.name;
res.json(user);
} else {
res.status(404).send('User not found');
}
});

// DELETE /users/:id - Delete a user by ID
router.delete('/users/:id', (req: Request, res: Response) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index !== -1) {
users.splice(index, 1);
res.status(204).send();
} else {
res.status(404).send('User not found');
}
});

export default router;

Now, import and use the routes in the main index.ts file:

import express from 'express';
import userRoutes from './routes/userRoutes';

const app = express();
const port = 3000;

app.use(express.json());
app.use('/api', userRoutes); // Prefix all routes with /api

app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});

Now you have CRUD operations for users available at /api/users.


Step 6: Create Models and Controllers

In more complex applications, it’s a good practice to separate models and controllers. For simplicity, weโ€™ll continue to keep everything in a single file structure. However, you can create separate folders for models and controllers as your application grows.


Step 7: Connect to a Database (Optional)

To store data persistently, you can integrate a database like MongoDB or PostgreSQL. For MongoDB, you can use Mongoose as an ORM. For PostgreSQL, use Sequelize or TypeORM. Hereโ€™s an example of using MongoDB:

  1. Install MongoDB and Mongoose:
npm install mongoose
  1. Set up a connection in index.ts:
import mongoose from 'mongoose';

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/express-ts-api', {
useNewUrlParser: true,
useUnifiedTopology: true,
});

Step 8: Set Up TypeScript Compilation

To compile your TypeScript code into JavaScript, add a build script in your package.json:

"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "nodemon -x ts-node src/index.ts"
}

This will allow you to run the project in development mode using npm run dev.


Step 9: Run the API

To run the API, use the following command:

npm run dev

This will start the server using ts-node and automatically restart it on file changes.


Conclusion

Congratulations! Youโ€™ve successfully built a REST API using Express and TypeScript. This setup gives you the benefits of static typing, which reduces runtime errors and improves code maintainability. You can now extend the API by adding more features, handling database interactions, implementing authentication, and much more.

Setting Up a Node.js Project with TypeScript

0
typscript course
typscript course

Table of Contents

  • Introduction
  • Prerequisites
  • Step 1: Install Node.js and npm
  • Step 2: Initialize a New Node.js Project
  • Step 3: Install TypeScript and Other Dependencies
  • Step 4: Configure TypeScript with tsconfig.json
  • Step 5: Write TypeScript Code
  • Step 6: Transpile TypeScript to JavaScript
  • Step 7: Run the Project
  • Step 8: Setting Up a Build Script for Continuous Development
  • Conclusion

Introduction

TypeScript is a statically typed superset of JavaScript that provides type safety and modern JavaScript features while still allowing you to compile to regular JavaScript. This makes it a great choice for Node.js development, as it adds type checking and other powerful features to enhance your coding experience.

In this article, we will walk through the steps to set up a Node.js project using TypeScript. By the end, you will have a fully functioning development environment where you can write, compile, and run TypeScript code in a Node.js application.


Prerequisites

Before we begin, ensure that you have the following tools installed on your machine:

  1. Node.js: You will need Node.js installed to manage packages and run your project.
  2. npm: Node Package Manager (npm) comes with Node.js and will be used to install dependencies.

To check if these tools are installed, open your terminal/command prompt and run:

node -v
npm -v

If Node.js and npm are not installed, you can download and install Node.js from here.


Step 1: Install Node.js and npm

If Node.js and npm are not installed, follow these steps to install them:

  1. Visit the Node.js website.
  2. Download the recommended LTS version for your operating system.
  3. Follow the installation instructions.

Once installed, verify the installation by checking the versions of Node.js and npm:

node -v
npm -v

Step 2: Initialize a New Node.js Project

Start by creating a new folder for your Node.js project. Navigate to the folder and initialize a new Node.js project:

mkdir my-node-ts-project
cd my-node-ts-project
npm init -y

This will generate a package.json file with default values.


Step 3: Install TypeScript and Other Dependencies

To set up TypeScript for your Node.js project, you need to install the TypeScript compiler and other dependencies. Run the following command:

npm install typescript @types/node --save-dev
  • typescript: This package includes the TypeScript compiler.
  • @types/node: This provides type definitions for Node.js, so TypeScript knows about Node’s built-in modules like fs, http, and more.

Additionally, you might want to install ts-node (for running TypeScript directly) and nodemon (for auto-reloading during development):

npm install ts-node nodemon --save-dev
  • ts-node: Allows you to run TypeScript code directly without compiling it first.
  • nodemon: Watches for file changes and restarts the application automatically.

Step 4: Configure TypeScript with tsconfig.json

TypeScript uses a configuration file called tsconfig.json to control how TypeScript behaves. To generate a tsconfig.json file, run:

npx tsc --init

This will generate a default tsconfig.json file. You can adjust this file according to your needs. Here’s a sample configuration for a Node.js project:

{
"compilerOptions": {
"target": "ES6", // Target JavaScript version
"module": "commonjs", // Use CommonJS modules (required for Node.js)
"outDir": "./dist", // Output directory for compiled files
"rootDir": "./src", // Root directory for TypeScript files
"esModuleInterop": true, // Enables compatibility with ES Modules
"strict": true, // Enable all strict type-checking options
"skipLibCheck": true, // Skip type checking of declaration files
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"], // Include all TypeScript files in the src directory
"exclude": ["node_modules"] // Exclude node_modules directory
}

Explanation of Important Fields:

  • "target": Specifies the JavaScript version to which TypeScript will compile. ES6 is typically a good choice for modern Node.js versions.
  • "module": For Node.js, use commonjs as the module system.
  • "outDir": Directory where the compiled JavaScript files will be placed.
  • "rootDir": Directory where the TypeScript source files are located (typically src).
  • "esModuleInterop": Ensures compatibility between CommonJS and ES Module imports.
  • "strict": Enables strict type-checking, which is highly recommended for better type safety.

Step 5: Write TypeScript Code

Now that TypeScript is set up, you can start writing TypeScript code. Create a folder named src in your project directory:

mkdir src

Inside the src folder, create a file called index.ts:

// src/index.ts

console.log("Hello, TypeScript and Node.js!");

// Function with TypeScript type annotations
const sum = (a: number, b: number): number => {
return a + b;
};

console.log("Sum of 5 and 7:", sum(5, 7));

Step 6: Transpile TypeScript to JavaScript

Once you’ve written your TypeScript code, you need to compile it into JavaScript using the TypeScript compiler (tsc). To compile the code, run:

npx tsc

This will compile your TypeScript files in the src folder and output the JavaScript files into the dist folder (as specified in the tsconfig.json).


Step 7: Run the Project

You can now run your compiled JavaScript code. Since we used commonjs as the module system, you can run the output JavaScript using Node.js:

node dist/index.js

Alternatively, if you installed ts-node earlier, you can run TypeScript files directly without compiling them manually:

npx ts-node src/index.ts

This will directly run your TypeScript code.


Step 8: Setting Up a Build Script for Continuous Development

To streamline the development process, you can set up a build script in your package.json to run the project more easily. Add the following to the "scripts" section of package.json:

{
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "nodemon -x \"ts-node\" src/index.ts"
}
}
  • build: Compiles TypeScript code to JavaScript using the TypeScript compiler.
  • start: Runs the compiled JavaScript code using Node.js.
  • dev: Uses nodemon to watch for file changes and run the TypeScript code using ts-node.

To run the project in development mode (with automatic reloading), you can now run:

npm run dev

Conclusion

By following these steps, you now have a Node.js project set up with TypeScript, providing you with the benefits of type safety and modern JavaScript features while running in a Node.js environment. You can now write, compile, and run TypeScript code with ease.

Additionally, using tools like ts-node and nodemon can significantly enhance your development experience by allowing you to run TypeScript directly and watch for file changes during development.

With this setup, you can confidently build scalable and type-safe applications using TypeScript and Node.js.

Namespaces vs Modules: Old vs Modern Approaches in TypeScript

0
typscript course
typscript course

Table of Contents

  • Introduction
  • What Are Namespaces in TypeScript?
  • What Are Modules in TypeScript?
  • Differences Between Namespaces and Modules
  • Advantages and Disadvantages of Namespaces
  • Advantages and Disadvantages of Modules
  • Best Practices for Using Namespaces and Modules
  • Conclusion

Introduction

In TypeScript, both Namespaces and Modules are used to organize code and manage dependencies. While Namespaces were the primary method of achieving modularity in the early versions of TypeScript, Modules (ESModules) have become the modern approach to modularizing code. With the widespread adoption of ESModules (ESM) and their integration into TypeScript, Namespaces have gradually fallen out of favor, though they still have their uses in specific scenarios.

This article will compare Namespaces and Modules, explain their differences, and explore the advantages and disadvantages of both approaches. By the end, you will have a clear understanding of when to use each and how they fit into the TypeScript ecosystem.


What Are Namespaces in TypeScript?

A Namespace in TypeScript is a way to organize code by grouping related functionality under a common name. In the early versions of TypeScript, namespaces were used to prevent global namespace pollution and provide a way to encapsulate code.

Namespaces are a TypeScript-specific feature that uses the namespace keyword to define a block of code that can contain variables, functions, interfaces, and classes.

Example of a Namespace:

namespace MathOperations {
export function add(a: number, b: number): number {
return a + b;
}

export function subtract(a: number, b: number): number {
return a - b;
}
}

// Accessing the functions inside the namespace
console.log(MathOperations.add(5, 3)); // Output: 8

Key Characteristics of Namespaces:

  • Encapsulation: Namespaces allow you to encapsulate related functionality inside a single object-like structure.
  • Internal to TypeScript: Namespaces are a TypeScript-specific concept and do not exist in the output JavaScript.
  • Global Pollution Mitigation: Namespaces help mitigate the problem of global namespace pollution by logically grouping code.
  • Requires the namespace Keyword: Namespaces are defined with the namespace keyword.

What Are Modules in TypeScript?

Modules in TypeScript refer to the ESModules (ECMAScript Modules) standard that is now supported natively by modern JavaScript and TypeScript. Modules allow you to organize code by splitting it into separate files. Each file is considered a module, and these modules can import and export functionality to be shared across different files.

In contrast to namespaces, Modules rely on the import and export syntax to expose and access functionality.

Example of a Module:

math.ts

export function add(a: number, b: number): number {
return a + b;
}

export function subtract(a: number, b: number): number {
return a - b;
}

app.ts

import { add, subtract } from './math';

console.log(add(5, 3)); // Output: 8

Key Characteristics of Modules:

  • File-based: Each module corresponds to a file, and the file serves as the boundary for the module’s scope.
  • Native in JavaScript: ESModules are a standard part of JavaScript, and modern JavaScript runtimes (e.g., browsers, Node.js) support them natively.
  • Static Imports/Exports: Modules use import and export statements to share functionality, making them easier to analyze and optimize during the bundling process.
  • Scoped by Default: Each module has its own scope, meaning variables, functions, and classes inside a module are private by default unless explicitly exported.

Differences Between Namespaces and Modules

1. Definition and Scope

  • Namespaces: Provide a way to group code in a single file or a set of files, and all the code inside a namespace is encapsulated under a single global object. The namespace acts as a container for the functionality.
  • Modules: Provide a way to split code into separate files, with each file being its own module. Modules are self-contained and have their own scope, meaning that variables and functions inside a module are not globally accessible unless explicitly exported.

2. File Structure

  • Namespaces: Do not rely on files; they can be used across multiple files, but they donโ€™t enforce a file-based structure. You can define a namespace in one file and use it in another file.
  • Modules: Enforce a file-based structure. Each file is treated as a module, and modules are imported or exported using the import and export syntax.

3. Compilation

  • Namespaces: Are a TypeScript-only feature, and they are compiled into a single JavaScript file with all the namespaces combined into a global object. There is no separate module system in the output JavaScript.
  • Modules: Are compiled into separate JavaScript files that are compatible with the ESModules standard. They use import and export statements, and TypeScript can convert them into different module formats such as CommonJS or AMD.

4. Import/Export Syntax

  • Namespaces: Accessing functionality within a namespace is done by referencing the namespace and its members. namespace MathOperations { export function add(a: number, b: number): number { return a + b; } } // Using the namespace MathOperations.add(5, 3);
  • Modules: Use import and export statements to access functionality across files. // math.ts export function add(a: number, b: number): number { return a + b; } // app.ts import { add } from './math';

Advantages and Disadvantages of Namespaces

Advantages:

  • No File Boundaries: You can organize and encapsulate code within a single file or across multiple files.
  • Simple to Use: The namespace keyword is simple and easy to understand for small-scale projects.
  • Avoids Global Pollution: Namespaces help avoid polluting the global namespace, as all functionality is encapsulated within the namespace.

Disadvantages:

  • Not Part of JavaScript: Namespaces are a TypeScript-only feature and do not exist in the output JavaScript, making it harder to work with in non-TypeScript environments.
  • Lack of Modularity: The lack of file-based separation means that larger codebases can become difficult to maintain.
  • Global Namespace Pollution: Even though namespaces encapsulate code, they still contribute to a global object, which can be problematic in larger applications.

Advantages and Disadvantages of Modules

Advantages:

  • File-Based Organization: Modules rely on a file-based structure, which naturally separates code into distinct, manageable pieces.
  • ESM Standard: Modules are part of the ESModules standard in JavaScript and are supported natively by modern browsers and JavaScript runtimes like Node.js.
  • Scoped by Default: Every module has its own scope, and you must explicitly export variables or functions to make them available outside the module.
  • Static Analysis and Optimization: Modules are easier to analyze by bundlers and can be optimized during the bundling process.

Disadvantages:

  • Requires Multiple Files: Larger codebases often require creating multiple files, which can become cumbersome if you don’t have proper folder structure.
  • Requires Bundling in Some Environments: Some environments (e.g., older browsers) require bundling tools (like Webpack or Rollup) to use modules, though this is increasingly less of an issue.

Best Practices for Using Namespaces and Modules

  • Prefer Modules for Modern Development: With the ESModules standard being widely adopted and supported in modern JavaScript environments, it’s generally recommended to use modules for new projects.
  • Namespaces for Legacy Code: If you’re working on a TypeScript project that interacts with legacy code or needs to avoid multiple files, namespaces can still be useful.
  • Avoid Mixing: Avoid mixing namespaces and modules in a large project to maintain consistency and avoid confusion. Choose one approach and stick to it throughout the project.

Conclusion

While Namespaces and Modules both provide ways to organize code in TypeScript, the modern approach is to use Modules. ESModules have become the standard for modular code in JavaScript and are now fully supported in TypeScript. Namespaces, while useful in certain scenarios, are an older TypeScript feature and are generally less suited for large-scale, modular applications.

For new projects, itโ€™s advisable to use Modules because of their file-based structure, compatibility with modern JavaScript, and better support for bundling and optimization tools. Namespaces can still be useful for small-scale or legacy projects, but for most modern development, Modules are the way to go.

Module Resolution: ESModules vs CommonJS in TypeScript

0
typscript course
typscript course

Table of Contents

  • Introduction
  • What is Module Resolution?
  • CommonJS Modules
  • ESModules (ESM)
  • Module Resolution in TypeScript
    • TypeScript Module Resolution Process
    • Module Resolution Strategies
  • ESModules vs CommonJS in TypeScript
    • Syntax Differences
    • Import/Export Compatibility
    • Default Exports
    • Interoperability Between ESM and CommonJS
  • Conclusion

Introduction

In modern JavaScript and TypeScript development, modular code is a fundamental building block. However, there are different ways to structure modules, which leads to different module systems. The two most prominent module systems in JavaScript today are CommonJS (CJS) and ESModules (ESM).

TypeScript, as a statically typed superset of JavaScript, supports both CommonJS and ESModules but requires specific handling to resolve and compile modules correctly. This article will explore the differences between CommonJS and ESModules in TypeScript, how module resolution works, and how these two systems interact.


What is Module Resolution?

Module resolution is the process by which TypeScript determines how to find and load external modules or packages in your project. This process is particularly important when using import and export syntax in your TypeScript code, as TypeScript needs to know how to locate the correct files, handle their types, and compile them into JavaScript.

Types of Module Systems

  • CommonJS (CJS): Primarily used in Node.js environments. Modules are typically imported using require() and exported using module.exports or exports.
  • ESModules (ESM): The standard JavaScript module system that is now supported in modern JavaScript environments, including browsers and Node.js. Modules are imported using the import and export syntax.

CommonJS Modules

CommonJS is the traditional module system used by Node.js and was introduced before the official ESModules specification. CommonJS modules use require() to import dependencies and module.exports or exports to export functionality.

Example of CommonJS Syntax:

Module 1: math.js

// Exporting in CommonJS
module.exports.add = function (a, b) {
return a + b;
};
module.exports.subtract = function (a, b) {
return a - b;
};

Module 2: app.js

// Importing in CommonJS
const math = require('./math');

console.log(math.add(5, 3)); // Output: 8

Key Characteristics of CommonJS:

  • Synchronous Loading: Modules are loaded synchronously, which works well in server environments like Node.js but can cause issues in browsers with many modules.
  • Exports: Modules export using module.exports or exports, with support for objects, functions, or primitives.

ESModules (ESM)

ESModules (ESM) is the modern standard for JavaScript modules. Introduced in ECMAScript 6 (ES6), it is now the preferred method for structuring code in both client-side and server-side JavaScript. ESModules use the import and export keywords to define dependencies and share functionality.

Example of ESModule Syntax:

Module 1: math.mjs

// Exporting in ESModules
export function add(a, b) {
return a + b;
}

export function subtract(a, b) {
return a - b;
}

Module 2: app.mjs

// Importing in ESModules
import { add, subtract } from './math.mjs';

console.log(add(5, 3)); // Output: 8

Key Characteristics of ESModules:

  • Asynchronous Loading: ESModules are designed to be loaded asynchronously, which is especially beneficial for front-end applications and modern bundling tools.
  • Syntax: Uses import and export to load and expose modules.
  • Static Analysis: ESModules are statically analyzable, allowing better optimization by bundlers and tools.

Module Resolution in TypeScript

TypeScript uses a module resolution strategy to figure out how to load and resolve dependencies. TypeScript supports multiple module systems, including CommonJS and ESModules.

TypeScript Module Resolution Process

When TypeScript compiles a project, it follows a set of steps to resolve module imports:

  1. File Lookup: TypeScript first tries to find the module in the node_modules folder or the paths specified in the tsconfig.json file.
  2. Extension Lookup: TypeScript checks for .ts, .tsx, .d.ts, .js, or .jsx extensions in that order when resolving modules.
  3. Fallback Strategy: If no specific file is found, TypeScript looks for index files like index.ts or index.js.

Module Resolution Strategies

  • Classic: This is the legacy resolution strategy that mimics how modules were resolved in earlier versions of TypeScript (pre-Node.js support).
  • Node: This strategy follows the module resolution logic that Node.js uses, making it ideal for server-side applications. It supports looking in node_modules, resolving relative paths, and handling package.json configurations like main and exports.

To enable the Node resolution strategy, you need to specify it in the tsconfig.json file:

{
"compilerOptions": {
"moduleResolution": "node"
}
}

ESModules vs CommonJS in TypeScript

Syntax Differences

The most obvious difference between CommonJS and ESModules is the syntax used to import and export modules.

  • CommonJS uses require() to import and module.exports to export.
  • ESModules uses import and export for module handling.

Example:

// CommonJS
const { add } = require('./math');

// ESModule
import { add } from './math';

Import/Export Compatibility

ESModules and CommonJS are not natively compatible with each other. TypeScript provides mechanisms to handle interoperation, but there are some important differences:

  • CommonJS exports are objects, which means that you canโ€™t directly import named exports as you would in ESModules. You would typically import the entire module and destructure it. // Importing CommonJS module in ESM style import * as math from './math';
  • ESModules default exports can be tricky when interoperating with CommonJS, because CommonJS modules export an object that contains all of the module’s exports. TypeScript will handle this with esModuleInterop flag.

To enable better interoperability between the two, you can use the esModuleInterop compiler option:

{
"compilerOptions": {
"esModuleInterop": true
}
}

This ensures smoother compatibility, especially when dealing with default exports.

Default Exports

In ESModules, default exports are explicitly marked with the default keyword. However, CommonJS does not have a formal concept of default exports and treats the entire module as an export object.

For example:

  • ESModules (Default export): // math.ts (ESM) export default function add(a: number, b: number): number { return a + b; }
  • CommonJS (Equivalent): // math.js (CJS) module.exports = function add(a, b) { return a + b; };

Interoperability Between ESM and CommonJS

TypeScript can manage interoperability between ESM and CommonJS with some configuration. For example, when working with a CommonJS module in an ESModule-based project, TypeScript requires you to use import syntax even if the CommonJS module is being used.

// Using CommonJS module in ESModule-style code
import * as math from './math'; // Works with esModuleInterop

TypeScript Configuration

In tsconfig.json, you can set the module system and resolution strategy to control how TypeScript resolves modules:

{
"compilerOptions": {
"module": "ESNext", // Use "ESNext" for ESModules or "CommonJS" for CJS
"moduleResolution": "node", // Enable Node.js module resolution
"esModuleInterop": true // Allow CommonJS modules to be imported with ESModule syntax
}
}

Conclusion

Understanding module resolution and the differences between ESModules and CommonJS in TypeScript is crucial for managing dependencies and building scalable applications. TypeScript allows developers to seamlessly use both module systems, but understanding their syntax, default export handling, and interoperation between the two is key to writing type-safe code.

When working with TypeScript, it’s essential to choose the right module system based on your project requirements. For modern web development, ESModules are preferred, while CommonJS is more suitable for Node.js server-side code.

By properly configuring your TypeScript project and understanding these module systems, you can ensure smooth module resolution and interoperability between different JavaScript libraries and projects.

Creating and Using Custom Type Declarations (.d.ts files)

0
typscript course
typscript course

Table of Contents

  • Introduction
  • What is a Declaration File?
  • Benefits of Using Custom Type Declarations
  • How to Create a .d.ts File
  • Example: Creating a Custom Declaration for a Library
  • Using Your Custom Type Declaration in a Project
  • Advanced Topics in Type Declarations
    • Declaring Modules
    • Declaring Global Types
    • Working with Ambient Declarations
  • Conclusion

Introduction

TypeScript is a powerful tool for developers, allowing for static type checking and making JavaScript development more reliable. While many popular libraries offer built-in TypeScript definitions, some libraries do not provide these definitions. In such cases, TypeScript allows developers to create their own type definitions using .d.ts files, which are essential for enhancing the type system for custom or third-party JavaScript libraries.

In this article, weโ€™ll explore how to create and use custom type declarations in TypeScript using .d.ts files. Whether you need to create type definitions for a custom module or a third-party library that lacks TypeScript support, this guide will provide a solid foundation for working with .d.ts files.


What is a Declaration File?

A .d.ts file, or declaration file, is a TypeScript file that provides type information for JavaScript code. These files contain type definitions, interfaces, and other type-related information that TypeScript can use to validate your code. They help TypeScript understand how a JavaScript library or module behaves, allowing you to get the benefits of type safety, autocompletion, and error checking even if the library doesn’t include TypeScript definitions.

A .d.ts file typically doesnโ€™t contain executable code. Itโ€™s just a way to describe the types in an existing JavaScript library.


Benefits of Using Custom Type Declarations

  • Improved Type Safety: Type declarations help TypeScript understand the types of objects, functions, and variables, reducing runtime errors caused by type mismatches.
  • Autocompletion: When you provide type definitions, your IDE can offer autocompletion, tooltips, and error checking, enhancing the developer experience.
  • Integration with TypeScript: If you’re integrating a JavaScript library or writing a custom library for others to use, providing type definitions ensures that users can work with it seamlessly in TypeScript projects.
  • Error Checking: Type declarations allow TypeScript to check for errors and type mismatches during development, improving overall code quality.

How to Create a .d.ts File

Creating a .d.ts file involves the following steps:

  1. Create the .d.ts file: Begin by creating a .d.ts file in your project. You can name it anything relevant, such as myLib.d.ts. Example: myLib.d.ts
  2. Declare the module: Use the declare module syntax to declare a module or library. This tells TypeScript that youโ€™re defining the types for a JavaScript library.
  3. Define the types: Inside the module declaration, define the types, functions, variables, or classes that the library exposes.
  4. Export types: If necessary, use the export keyword to make types available for use in other parts of your TypeScript project.

Example: Creating a Custom Declaration for a Library

Letโ€™s walk through an example where youโ€™re creating a .d.ts file for a fictional JavaScript library called myLib. Suppose this library provides a function called greet, which takes a string as an argument and returns a string.

  1. Create the .d.ts file: myLib.d.ts
  2. Declare the module: declare module 'myLib' { export function greet(name: string): string; }

In this example:

  • declare module 'myLib' declares the module, indicating that this file provides type information for the myLib module.
  • export function greet(name: string): string defines the function greet as accepting a string and returning a string.

Using Your Custom Type Declaration in a Project

Once you have your .d.ts file, you can use it in your TypeScript project. TypeScript will automatically pick up the type definitions and provide type checking for the myLib module.

  1. Install or reference your custom module: If youโ€™re working with a third-party library or custom module, import the module as usual in your TypeScript file. import { greet } from 'myLib'; const message = greet('TypeScript'); console.log(message); // Output: "Hello, TypeScript!"
  2. Type Checking and Autocompletion: TypeScript will now be able to check the types for the greet function, ensuring you pass the correct arguments and return the expected result.

Advanced Topics in Type Declarations

Declaring Modules

In cases where you’re declaring a module or library, TypeScript needs to know how to treat its imports. You can declare a module with the declare module keyword to specify how it should behave.

For example:

declare module 'myLib' {
export function greet(name: string): string;
export const version: string;
}

This declaration includes not only a function but also a constant (version). By declaring the module, TypeScript will understand the module’s API, allowing for better type checking and autocompletion.

Declaring Global Types

Sometimes, you need to define global types or variables that can be accessed across the entire application. To achieve this, you can create a .d.ts file that modifies the global scope.

For example, if you wanted to declare a global interface:

// global.d.ts
declare global {
interface Window {
myLibrary: any;
}
}

export {};

In this example, the declare global block extends the Window interface, adding a myLibrary property globally.

Working with Ambient Declarations

Ambient declarations allow you to define types for external JavaScript libraries without modifying the original code. For example:

// ambient.d.ts
declare var myGlobalVar: string;
declare function myGlobalFunction(param: number): void;

This type of declaration provides TypeScript with the necessary type information without directly interacting with the JavaScript code.


Conclusion

Custom type declarations using .d.ts files are an essential tool in TypeScript development. They allow developers to add type safety, autocompletion, and error checking for third-party JavaScript libraries that donโ€™t include TypeScript definitions. Whether youโ€™re working with an external library or writing your own, creating type definitions helps integrate the library with TypeScript and enhances the developer experience.

By understanding how to declare types, modules, and global variables, you can create rich, type-safe environments even for JavaScript code that doesn’t natively support TypeScript. Whether you’re building a project from scratch or using an external library, .d.ts files ensure that TypeScript can provide its full benefits.