ntcoding/typescript-backend-project-setup
Sets up NX monorepo for TypeScript backend projects optimized for AI-assisted development. Delegates to NX commands where possible, patches configs as last resort. Triggers on: 'set up typescript backend project', 'create backend project', 'initialize typescript backend', 'create monorepo', or when working in an empty project folder.
npx skills add https://github.com/NTCoding/claude-skillz --skill typescript-backend-project-setup
> 🚨 DO NOT USE PLAN MODE. This skill IS the plan. Follow the steps exactly as written.
> ⚠️ Check NX docs for latest conventions: https://nx.dev/docs/getting-started/start-new-project
> NX evolves quickly. Verify these instructions against current NX best practices before use.
Set up NX monorepo for TypeScript backend projects with maximum type safety, strict linting, 100% test coverage, and AI-optimized project structure.
10. Phase 10: Document Architecture - Optional interview
This skill uses a template located at: typescript-backend-project-setup/template/
The template contains only files NX cannot create: Claude Code integration, documentation structure, and git hooks.
Before starting, ask the user for the full path to the claude-skillz repository so you can locate the template.
Ask the user:
Priority: Commands > Installs > Patch files (last resort)
Run the NX workspace generator:
npx create-nx-workspace@latest [workspace-name] --preset=ts --pm=pnpm --nxCloud=skip --interactive=false
This creates:
nx.json - NX configurationtsconfig.base.json - Base TypeScript configpackage.json - Root package with NX scriptspnpm-workspace.yaml - Workspace definition.gitignore - Standard ignoresPatch .gitignore - Add test-output:
Add test-output to .gitignore (vitest coverage output):
test-output
Checkpoint: Verify nx report shows NX version.
Add testing and code quality tools:
# Add NX plugins
nx add @nx/vitest
nx add @nx/eslint
nx add @nx/node # Required for creating applications
# Install testing dependencies
pnpm add -D vitest @vitest/coverage-v8
# Install ESLint dependencies (required for strict config)
pnpm add -D typescript-eslint @nx/eslint-plugin eslint-plugin-functional
# Install git hooks
pnpm add -D husky lint-staged
Adding @nx/vitest. Provides integrated test runner with coverage reporting.
Adding @nx/eslint. Provides consistent linting across all projects.
Adding @nx/node. Required for creating Node.js applications.
Adding husky and lint-staged. Provides pre-commit verification gate.
If user specified packages in Phase 1, create them:
# For each package (publishable library with vitest)
nx g @nx/js:library packages/[pkg-name] --publishable --importPath=@[workspace-name]/[pkg-name] --bundler=tsc --unitTestRunner=vitest
If user specified apps in Phase 1, create them:
# For each app (node application - vitest NOT supported, use none)
nx g @nx/node:application apps/[app-name] --unitTestRunner=none
🚨 IMPORTANT:
@nx/js:library supports --unitTestRunner=vitest@nx/node:application only supports --unitTestRunner=jest|none (NOT vitest)After creating projects, run nx sync to update TypeScript project references.
Copy template files (only what NX can't create):
Claude Code Integration:
cp -r [claude-skillz-path]/typescript-backend-project-setup/template/CLAUDE.md [target-directory]/
cp -r [claude-skillz-path]/typescript-backend-project-setup/template/AGENTS.md [target-directory]/
cp -r [claude-skillz-path]/typescript-backend-project-setup/template/.claude [target-directory]/
Adding CLAUDE.md. Provides AI context, commands, and project conventions.
Adding .claude/settings.json. Provides permission guardrails and hook configuration.
Adding .claude/hooks/block-dangerous-commands.sh. Prevents destructive git operations (--force, --hard, --no-verify).
Documentation Structure:
cp -r [claude-skillz-path]/typescript-backend-project-setup/template/docs [target-directory]/
cp [claude-skillz-path]/typescript-backend-project-setup/template/repository-setup-checklist.md [target-directory]/
Adding docs/conventions/. Provides coding standards and workflow documentation.
Adding docs/architecture/. Provides system design and domain terminology templates.
Adding docs/project/. Provides project vision and planning templates.
Git Hooks:
cp -r [claude-skillz-path]/typescript-backend-project-setup/template/.husky [target-directory]/
Adding .husky/pre-commit. Provides pre-commit verification (lint, typecheck, test).
Custom ESLint Rules:
cp -r [claude-skillz-path]/typescript-backend-project-setup/template/.eslint-rules [target-directory]/
Adding .eslint-rules/no-generic-names.js. Custom rule that bans generic names (utils, helpers, service, manager) in filenames and class names.
Make scripts executable:
chmod +x [target-directory]/.claude/hooks/block-dangerous-commands.sh
Replace placeholders in copied files:
| Placeholder | Replace With |
|-------------|--------------|
| {{WORKSPACE_NAME}} | User's workspace name |
| {{WORKSPACE_DESCRIPTION}} | User's domain description |
| {{DOMAIN_NAME}} | User's workspace name (used as context name in glossary) |
| {{DOMAIN_DESCRIPTION}} | User's domain description |
Files with placeholders:
CLAUDE.mddocs/conventions/codebase-structure.mddocs/architecture/domain-terminology/contextive/definitions.glossary.ymldocs/project/project-overview.mdThese patches add our strict standards to NX-generated configs.
Patch nx.json - Add lint dependency to build/test:
Add to targetDefaults.build.dependsOn:
"dependsOn": ["lint", "^build"]
Add to targetDefaults.test:
"dependsOn": ["lint"]
This ensures AI gets immediate lint feedback on any change.
Patch tsconfig.base.json - Add strict TypeScript flags:
Add these to compilerOptions:
{
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"exactOptionalPropertyTypes": true,
"verbatimModuleSyntax": true
}
Patch eslint.config.mjs - Add strict rules:
IMPORTANT: Completely overwrite eslint.config.mjs with this exact content (do not merge, do not patch - replace the entire file):
import nx from '@nx/eslint-plugin';
import tseslint from 'typescript-eslint';
import noGenericNames from './.eslint-rules/no-generic-names.js';
const customRules = {
plugins: {
custom: {
rules: {
'no-generic-names': noGenericNames,
},
},
},
};
export default tseslint.config(
...nx.configs['flat/base'],
...nx.configs['flat/typescript'],
...nx.configs['flat/javascript'],
{
ignores: ['**/dist', '**/out-tsc', '**/node_modules', '**/.nx', '*.config.ts', '*.config.mjs', '*.config.js', 'vitest.workspace.ts'],
},
customRules,
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
// Custom rule: no generic names
'custom/no-generic-names': 'error',
// No comments - forces self-documenting code
'no-warning-comments': 'off',
'multiline-comment-style': 'off',
'capitalized-comments': 'off',
'no-inline-comments': 'error',
'spaced-comment': 'off',
// Ban let - use const only
'no-restricted-syntax': [
'error',
{
selector: 'VariableDeclaration[kind="let"]',
message: 'Use const. Avoid mutation.',
},
],
'prefer-const': 'error',
'no-var': 'error',
// No any types
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
'@typescript-eslint/no-unsafe-call': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
// No type assertions - fix the types instead
'@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'never' }],
// Ban generic folder imports (not lib - that's NX convention)
'no-restricted-imports': [
'error',
{
patterns: [
{ group: ['*/utils/*', '*/utils'], message: 'No utils folders. Use domain-specific names.' },
{ group: ['*/helpers/*', '*/helpers'], message: 'No helpers folders. Use domain-specific names.' },
{ group: ['*/common/*', '*/common'], message: 'No common folders. Use domain-specific names.' },
{ group: ['*/shared/*', '*/shared'], message: 'No shared folders. Use domain-specific names.' },
{ group: ['*/core/*', '*/core'], message: 'No core folders. Use domain-specific names.' },
],
},
],
// Complexity limits
'max-lines': ['error', { max: 400, skipBlankLines: true, skipComments: true }],
'max-depth': ['error', 3],
'complexity': ['error', 12],
// Naming conventions
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'variable',
format: ['camelCase'],
},
{
selector: 'variable',
modifiers: ['const'],
format: ['camelCase', 'UPPER_CASE'],
},
{
selector: 'function',
format: ['camelCase'],
},
{
selector: 'parameter',
format: ['camelCase'],
leadingUnderscore: 'allow',
},
{
selector: 'typeLike',
format: ['PascalCase'],
},
{
selector: 'enumMember',
format: ['PascalCase'],
},
{
selector: 'objectLiteralProperty',
format: null,
},
],
},
},
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
}
);
This enforces:
let - Only const allowed via no-restricted-syntaxany types - AnywherePatch package.json - Add scripts and lint-staged:
Add these to the root package.json:
{
"scripts": {
"build": "nx run-many -t build",
"test": "nx run-many -t test",
"lint": "nx run-many -t lint",
"typecheck": "nx run-many -t typecheck",
"verify": "nx run-many -t lint,typecheck && nx run-many -t test --coverage",
"prepare": "husky"
},
"lint-staged": {
"*.ts": ["eslint --fix"]
}
}
Patch vitest.config.mts files - Add 100% coverage thresholds:
For EACH project created in Phase 4 that has a vitest.config.mts, add thresholds to the coverage block:
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8' as const,
thresholds: {
lines: 100,
statements: 100,
functions: 100,
branches: 100,
},
},
This enforces 100% test coverage - tests will FAIL if coverage drops below 100%.
Copy content from claude-skillz skills to the docs:
Testing conventions:
[claude-skillz-path]/writing-tests/SKILL.mddocs/conventions/testing.mdAdding docs/conventions/testing.md. Provides test naming, assertion patterns, and edge case checklists.
Software design conventions:
[claude-skillz-path]/software-design-principles/SKILL.mddocs/conventions/software-design.mdAdding docs/conventions/software-design.md. Provides object calisthenics, fail-fast, and dependency inversion patterns.
Initialize husky, then overwrite the default pre-commit with our version:
cd [target-directory]
npx husky init
# husky init creates a default pre-commit - overwrite it with ours:
cp [claude-skillz-path]/typescript-backend-project-setup/template/.husky/pre-commit .husky/pre-commit
# Check NX is working
nx report
# View empty workspace
nx graph
Review the repository-setup-checklist.md and ensure all items are checked.
Checkpoint: All verification commands pass. Ready for first commit.
Offer to interview the user to fill in placeholder content:
Architecture Overview (docs/architecture/overview.md):
Domain Terminology (docs/architecture/domain-terminology/contextive/definitions.glossary.yml):
Project Overview (docs/project/project-overview.md):
This skill creates an NX monorepo using a command-first approach:
create-nx-workspace for foundationnx add for pluginspnpm add for dependenciesThe result provides:
For adding projects after setup, see docs/conventions/codebase-structure.md.
Trigger: "verify typescript setup", "check project setup", "audit monorepo config"
Use this to verify an existing repo has all required configurations.
find . -name "package.json" -not -path "*/node_modules/*" -not -path "*/.nx/*"
Read eslint.config.mjs and verify it contains:
.eslint-rules/no-generic-names.jscustom/no-generic-names: 'error'no-restricted-syntax with VariableDeclaration[kind="let"] selector@typescript-eslint/consistent-type-assertions with assertionStyle: 'never'no-restricted-imports with patterns for utils, helpers, common, shared, coremax-lines: 400, max-depth: 3, complexity: 12If any missing: List what's missing and offer to fix.
For EACH project discovered in Step 1:
vitest.config.mts exists in that project directorycoverage.thresholds contains:lines: 100statements: 100functions: 100branches: 100If any project missing thresholds: List which projects are non-compliant and offer to fix.
.husky/pre-commit contains lint-staged and verifypackage.json has lint-staged config.gitignore contains test-output on its own lineOutput a summary:
✓ ESLint: All rules configured
✗ Vitest: 2/6 projects missing coverage thresholds
- apps/eclair
- apps/docs
✓ Git Hooks: Configured
✓ Gitignore: test-output ignored
If failures: Offer to fix all issues automatically.
Take ntcoding/typescript-backend-project-setup from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npx, pnpm.
Without those the skill loads but fails at the first command.