Creating contract package using Typescript and Github Packages
Tags: typescript, webdev, tutorial, npm
Published: 2023-06-24 07:15:13

Imagine you have cool frontend app that is connected with your awesomome backend application that serves an API. The API is obviously expecting some input data in some of the requests, and sometimes it returns some data in response. I say some, because your web app is not aware what kind of data is supposed to send and what should expect in return. So today I will show you how to create contract package with [Typescript](https://www.typescriptlang.org/) and deploy it in [Github Packages](https://docs.github.com/en/packages).

**NOTE:** We won't be covering here how to create backend nor frontend application, so the contract package will be in seperate repository. You could create monorepo with api and contracts.

You will need: 
* [Node.js](https://nodejs.org/en)
* [NPM](https://www.npmjs.com/)
* [Github account](https://github.com/)
* [Typescript](https://www.typescriptlang.org/)

## Outline
Let's say we have a website in which the user must be logged in order to use it. Frontend wants to fetch basic info about the user. The API is providing such endpoint and it returns an object of type `UserOutput`

```typescript
interface UserOutput { 
  username: string;
  email: string;
}
```

What should we do so that web app has access to given type?

## Creating new project

Let's go straight to the point.

First create a directory for new project

```bsh
mkdir my-project-contracts
cd my-project-contracts
```  

Initialize new npm project with git and typescript (for now skip all the settings in `package.json`)

```bsh
git init
npm init
npm i typescript --save-dev
npx tsc --init
```
Create `UserOutput.ts` file and add inside your first contract

```typescript
export default interface UserOutput {
  username: string;
  email: string;
}
``` 

Create `index.ts` file and import your contract

```typescript
import UserOutput from "./UserOutput";
export { UserOutput };
```

Now we tweak `tsconfig.json`. For contracts it's sufficient to emit only declaration files. We specify output directory to `dist` folder. So our config file should look like this

``` typescript
{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */
    /* 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. */
    "outDir": "dist",                                   /* Specify an output folder for all emitted files. */
  }
}
```
Let's not forget about `.gitignore` in order to not publish unnecessary folders

```
/dist
/node_modules
```

So far so good!

Now it's time to prepare our project to enable it for publishing as a package in [Github Package](https://docs.github.com/en/packages/quickstart) registry with usage of [Github Actions](https://docs.github.com/en/actions).

Before we go forward we need to set up personal access token (classic). To do that follow the steps [here](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic). 
Make sure you select the scopes which define the access for personal tokens. 
**IMPORTANT:** Keep your token for later! We're gonna use it ;)

![Scopes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y2tox7d3rfr72il26uhb.png)

Ok, when it's done, we come back to our project once again to finish the setup.

Change your `package.json` so that:
* `name` follows `@YOUR-USERNAME/YOUR-REPOSITORY`
* set the test script to `exit 0`
* set build command to build `*.d.ts` files
* tell npm which scope and registry to publish packages by specifying the `publishConfig` key 
* specify version of the package
* use `files` key to indicate with files should be included when publishing (read more about [here](https://stackoverflow.com/questions/31642477/how-can-i-publish-an-npm-package-with-distribution-files))
in my case it looks like this:

```json
{
  "name": "@gh-username/my-project-contracts",
  "version": "1.0.0",
  "description": "Example of creating package with contracts usin Github Packages",
  "types": "./dist/index.d.ts",
  "publishConfig": {
    "gh-username:registry": "https://npm.pkg.github.com"
  },
  "files": [
    "dist"
  ],
  "scripts": {
    "test": "exit 0",
    "build": "npx tsc"
  },
  "author": "me",
  "devDependencies": {
    "typescript": "^5.1.3"
  }
}
```

And the last thing to do in our project is to set up pipeline for building and publishing it. So, following [Quickstart example](https://docs.github.com/en/packages/quickstart):

1. create a `.github/workflows` directory. In that directory, create a file named `release-package.yml`.

2. copy the following YAML content into the `release-package.yml` file.

```yml
name: My package with contracts

on:
  release:
    types: [created]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - run: npm ci
      - run: npm run build
      - run: npm test

  publish-gpr:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      packages: write
      contents: read
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
          registry-url: https://npm.pkg.github.com/
      - run: npm ci
      - run: npm run build
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}

```

As you can see, the aceess token we set up earlier, is used to check up [permissions for package registries](https://docs.github.com/en/packages/learn-github-packages/about-permissions-for-github-packages#about-scopes-and-permissions-for-package-registries).

Aaaand looks like we've done! Well, almost, now you have to create a repository in Github and push your changes. 

## Create release
If you finished creating new repo for contracts, you'd be ready to create a release and build your new package.

1. On GitHub.com, navigate to the main page of the repository.
2. To the right of the list of files, click **Releases**.
3. At the top of the page, click **Draft a new release**.
4. Choose a tag and a title for the release.
5. Click **Publish release**. 
6. Go to the **Actions** tab and see you release action


![release action](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ikfpfcty4e6xm3q1sw2v.png)

After it finishes, in the main page of repository you should see your package

![packages](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6fcsnymeujmljyq6fzdn.png)

**NOTE:** For more detailed description about creating releases, check [official documentation](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release).

##Final touch: Use contract package in your project

We almost there! In the project you want to use the contract package, set `.npmrc` file with information about github registry 


```.npmrc
//npm.pkg.github.com/:_authToken={GITHUB_TOKEN}
@gh-username:registry=https://npm.pkg.github.com
```
**NOTE:** Create new `GITHUB_TOKEN` only with `read:packages` scope.

If you use `npm`, install your packages with `npm i @YOUR-USERNAME/YOUR-REPOSITORY --save-dev`.

If you use `yarn` you will have to point with url to your repository, check [conversation on Stack Overflow](https://stackoverflow.com/questions/43411864/how-to-install-package-from-github-repo-in-yarn).

And that's it! Thank you for taking your time :)