Building an SDK with TypeScript

Building an SDK with TypeScript

An SDK (Software Development Kit) is a collection of software development tools in one installable package. An SDK can include libraries, APIs, and sample code for a specific platform or programming language. SDKs are used to help developers build applications for a particular platform. In this article, we will go through the steps of building an SDK using TypeScript.

Getting Started with TypeScript and Node.js

TypeScript is a superset of JavaScript that adds optional type annotations and class-based object-oriented programming to the language. TypeScript is easy to adopt for those familiar with JavaScript.

Node.js is a popular platform for building server-side applications. It's fast and efficient, making it ideal for building an SDK.

You can install TypeScript in your local development environment by running the following command:

npm install -g typescript

Setting up Repository and Configuration

So, Let's start with the implementation.

Let's initialize our node js project using npm init -y

Install the necessary dependencies:

npm install --save-dev microbundle 
npm install isomorphic-unfetch

microbundle is a zero-configuration bundler for small libraries and web components. It is used to package and distribute your code in a format that can be used in different environments.

isomorphic-unfetch is a library that implements the browser's Fetch API to be used on both the client and server. This library allows you to make HTTP requests in a way that is consistent across both client and server-side JavaScript.

Next, you need to initialize TypeScript using the following command:

tsc --init

This will create a tsconfig.json file in your project, which is the configuration file for TypeScript. You can use this file to specify the options for your TypeScript compiler.

Ensure you modify the tsconfig.json to have the below values

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": false,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "test", "lib", "**/*spec.ts"]
}

Code Implementation

Create folders and files as per the below structure

sdk/
├── src/
│   ├── resources/
│   │   ├── base.ts
│   │   ├── posts/
│   │   │   ├── index.ts
│   │   │   └── types.ts
│   │   └── users/
│   │       ├── index.ts
│   │       └── types.ts
│   └── index.ts
├── tsconfig.json
└── package.json

Base.ts

// src/resources/base.ts

import fetch from 'isomorphic-unfetch';

type Config = {
  apiKey: string;
  baseUrl?: string;
};

export abstract class Base {
  private apiKey: string;
  private baseUrl: string;

  constructor(config: Config) {
    this.apiKey = config.apiKey;
    this.baseUrl = config.baseUrl || 'https://jsonplaceholder.typicode.com';
  }

  protected request<T>(endpoint: string, options?: RequestInit): Promise<T> {
    const url = `${this.baseUrl}${endpoint}`;
    const headers = {
      'Content-Type': 'application/json',
      'api-key': this.apiKey,
    };
    const config = {
      ...options,
      headers,
    };

    return fetch(url, config).then((response) => {
      if (response.ok) {
        return response.json();
      }
      throw new Error(response.statusText);
    });
  }
}

The Base class is an abstract class that provides common functionality for all resources in our SDK. It takes in a Config object with apiKey and baseUrl properties in its constructor which is common and needed for API invocation. The request the method is used to make HTTP requests to the API. This method takes in an endpoint and an optional RequestInit the object for additional configuration. The method returns a Promise with the response data of the type T.

Resource Types and Implementation

Next, let's create the respective resources like posts , users along with their types in the resources/{resource} folder. Below is an example of posts.

Follow similar implementation for other resources as needed.

// src/resources/posts/types.ts

export declare type Post = {
  id: number;
  title: string;
  body: string;
  userId: number;
};

export declare type NewPost = {
  title: string;
  body: string;
  userId: number;
};
// src/resources/posts/index.ts

import { Base } from '../base';
import { NewPost, Post } from './types';

const resourceName = 'posts';

export class Posts extends Base {
  getPostById(id: number): Promise<Post> {
    return this.request(`/${resourceName}/${id}`);
  }

  getPosts(): Promise<Post[]> {
    return this.request(`/${resourceName}`);
  }

  createPost(newPost: NewPost): Promise<Post> {
    return this.request(`/${resourceName}`, {
      method: 'POST',
      body: JSON.stringify(newPost),
    });
  }
}

The types.ts file contains the TypeScript interfaces for resources likePost, NewPost. These objects will be used throughout the SDK. These interfaces provide a blueprint for what kind of data each object should contain.

Entry Point: index.ts

// src/index.ts

import { Posts } from './resources/posts';
import { Users } from './resources/users';

export class Library {
  posts: Posts;
  users: Users;

  constructor(config: { apiKey: string; baseUrl?: string }) {
    this.posts = new Posts(config);
    this.users = new Users(config);
  }
}

This file is the entry point of the SDK. It exports a Library class that acts as a central point of access to all the resources. The constructor of this class takes a config object that contains the apiKey and baseUrl. The apiKey is used for authentication and the baseUrl is used to specify the base URL for the API endpoints. This class creates instances of the Posts and Users classes and makes them available as properties.

Package JSON

In the package.json , add the main , module, unpkg and types as shown below. Also, add build within the scripts object to remove the earlier dist folder and generate the resultant files using the microbundle library.

{
  "name": "sdk",
  "version": "1.0.0",
  "description": "",
  "main": "dist/index.js",
  "module": "dist/index.m.js",
  "unpkg": "dist/index.umd.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "rm -rf dist && microbundle --tsconfig tsconfig.json --no-sourcemap"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "microbundle": "^0.15.1"
  },
  "dependencies": {
    "isomorphic-unfetch": "^4.0.2"
  }
}

Build and Generate the final files

npm run build

This will use the microbundle library to create different formats of the code in the dist folder.

Publish to NPM Registry

  • Create an account on NPM Registry.

  • Login to your npm account using the following command: npm login

  • In the package.json, add the below fields

      {
      ...
          "exports": {
              "require": "./dist/index.js",
              "default": "./dist/index.modern.js"
          },
          "files": [
              "dist"
          ]
      }
    
  • To publish to the registry, run npm publish

Test SDK

Install the package from the registry. If not published, you can also make use of `npm link <path_to_sdk> within your node-based test repository.

You can consume using the below:

import { Library } from '<npm_repo>';

const client = new Library({
  apiKey: 'your-api-key',
  baseUrl: 'https://jsonplaceholder.typicode.com',
});

client.posts
  .createPost({
    body: 'Lorem Epsum',
    title: 'How to ?',
    userId: 1,
  })
  .then((p) => {
    console.log(`Created new post with id ${p.id}`);
  });

client.users.getUserById(1).then((a) => console.log(a));

Conclusion

Building an SDK using TypeScript is a great way to package and reuse functionality. By creating a set of entities that can be used together, you can provide a convenient and easy-to-use interface for your users. With TypeScript, you can provide type definitions and ensure that the code is more maintainable and easier to debug.

In conclusion, we have covered the steps on how to create an SDK using TypeScript and how to publish it to the npm registry. We hope you found this article helpful. If you liked it, please consider following, liking, and sharing it with your friends and colleagues. Your support helps us create more valuable content for you.

Did you find this article valuable?

Support Durgadas Kamath by becoming a sponsor. Any amount is appreciated!