Adding a Package
This guide explains how to create a new shared TypeScript package under packages/ and make it available to all apps and other packages via the @gospelib/* scope.
When to Create a Package
Create a shared package when:
- Multiple apps need the same code (types, utilities, components)
- You want a clean contract boundary between layers
- The code has no service-specific business logic
tip
If the code is only used by one app, keep it local to that app. Extract to a package when a second consumer appears.
Existing Packages
| Package | Purpose |
|---|---|
@gospelib/types | Shared TypeScript types (auto-generated from OpenAPI + manual) |
@gospelib/ui | Cross-platform React Native component library |
@gospelib/sdk | Client SDK using openapi-fetch |
@gospelib/config | ESLint/TSConfig presets + Zod env schemas |
@gospelib/testing | Shared test fixtures, mocks, and helpers |
Step-by-Step
1. Create the directory structure
packages/<name>/
├── src/
│ └── index.ts # Barrel export
├── package.json
├── project.json
├── tsconfig.json
└── vitest.config.ts # If the package has tests
2. Create package.json
{
"name": "@gospelib/<name>",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"test": "vitest run",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.5"
}
}
info
Packages use "private": true because they are consumed via pnpm workspace protocol, not published to npm.
3. Create tsconfig.json
Extend the base config:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
4. Create project.json
Register the package with Nx:
{
"name": "<name>",
"projectType": "library",
"sourceRoot": "packages/<name>/src",
"targets": {
"test": {
"command": "cd packages/<name> && vitest run"
},
"lint": {
"command": "cd packages/<name> && eslint ."
},
"typecheck": {
"command": "cd packages/<name> && tsc --noEmit"
}
}
}
5. Add the path alias
In tsconfig.base.json at the repo root, add:
{
"compilerOptions": {
"paths": {
"@gospelib/<name>": ["packages/<name>/src/index.ts"]
}
}
}
6. Write your barrel export
// packages/<name>/src/index.ts
export { someFunction } from './someModule';
export type { SomeType } from './types';
Consume the Package
In any app or package that depends on it:
# From the repo root
pnpm add @gospelib/<name> --filter @gospelib/web --workspace
This adds a workspace protocol dependency:
{
"dependencies": {
"@gospelib/<name>": "workspace:*"
}
}
For Next.js apps, add the package to transpilePackages in next.config.ts:
const nextConfig: NextConfig = {
transpilePackages: ['@gospelib/<name>'],
};
Dependency Rules
Follow these constraints to keep the dependency graph clean:
- Apps can import any package
@gospelib/sdkimports only@gospelib/types- Packages should not import app code
- Services (Go/Python) never import TS packages at runtime
Verify It Works
- Run
pnpm installfrom the repo root to link the workspace package - Import from a consumer:
import { something } from '@gospelib/<name>' - Type check:
pnpm typecheck - Run tests:
cd packages/<name> && pnpm test
Checklist
-
package.jsonwith@gospelib/<name>name and"private": true -
tsconfig.jsonextending../../tsconfig.base.json -
project.jsonwith Nx targets - Path alias in
tsconfig.base.json - Barrel export in
src/index.ts - Consumer added to
transpilePackages(if Next.js) - Commit scope added to
commitlint.config.mjs - Release Please component added to
release-please-config.json