Projen is a project management tool that generates and manages your project configuration files, offering a programmatic way to handle infrastructure as code.

Let’s see projen in action. Imagine you’re starting a new Node.js project with a TypeScript backend and want to configure it for AWS CDK. Instead of manually creating package.json, tsconfig.json, .gitignore, and other boilerplate, projen handles it.

Here’s how you’d initialize a new project:

mkdir my-cdk-project
cd my-cdk-project
npx projen new --from typescript --cdk

This command creates a new directory my-cdk-project and inside it, a .projenrc.js file. This .projenrc.js file is the heart of your projen configuration. It describes your project’s structure and dependencies.

The output of that command would look something like this in your .projenrc.js:

const { TypeScriptProject } = require('projen');

const project = new TypeScriptProject({
  name: 'my-cdk-project',
  defaultReleaseBranch: 'main',
  // ... other configurations
});

// Add CDK dependencies
project.addDeps('@aws-cdk/core', '@aws-cdk/aws-s3');

// Add CDK dev dependencies
project.addDevDeps('aws-cdk');

project.synth();

When you run npx projen, it reads this .projenrc.js file and generates all the necessary project files, like package.json, tsconfig.json, .eslintrc.json, and .gitignore.

For example, the generated package.json would include the CDK dependencies you specified:

{
  "name": "my-cdk-project",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "lint": "eslint . --ext .ts,.tsx",
    "cdk": "cdk"
  },
  "dependencies": {
    "@aws-cdk/aws-s3": "^1.100.0", // Example version
    "@aws-cdk/core": "^1.100.0"   // Example version
  },
  "devDependencies": {
    "aws-cdk": "^1.100.0",        // Example version
    "eslint": "^7.0.0",
    "jest": "^26.0.0",
    "typescript": "^4.0.0"
  }
}

And the tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "outDir": "dist",
    "target": "ES2020",
    "module": "commonjs",
    "lib": [
      "es2020"
    ],
    "sourceMap": true,
    "rootDir": "src",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "declaration": true
  },
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

The core problem projen solves is configuration drift and the tediousness of maintaining boilerplate across multiple projects. Instead of manually editing dozens of files when you need to update a linter rule or add a new dependency, you update your .projenrc.js and run npx projen. It then regenerates all the affected files, ensuring consistency and saving you immense time.

Projen uses "synthesizers" to generate different types of project files. The TypeScriptProject synthesizer, for example, knows how to generate typical TypeScript project files. You can also use other synthesizers for different project types (e.g., NodeProject, ReactProject) or even create your own.

When you add CDK constructs to your project, projen can automatically configure the necessary build and linting steps. For instance, if you add @aws-cdk/aws-lambda, projen will ensure your tsconfig.json is set up to compile TypeScript to JavaScript, and your package.json will have a cdk script that points to the cdk executable.

The real power comes when you start managing multiple related projects. Projen has a "project hierarchy" feature where a parent project can manage multiple child projects. This is incredibly useful for monorepos or when you have a core library used by several applications. You define the parent project’s .projenrc.js, and it can then include child projects, each with their own .projenrc.js or managed directly by the parent.

For example, a parent .projenrc.js might look like this:

const { JsiiProject } = require('projen');
const { AwsCdkProject } = require('projen'); // Assuming you have a custom CDK project type

const parent = new JsiiProject({
  name: 'my-organization-projects',
  devDeps: ['projen'],
  // ...
});

const cdkApp = new AwsCdkProject({ // This would be a custom project type or a configured JsiiProject
  parent: parent,
  name: 'my-cdk-app',
  // ... CDK specific configurations
});

const lambdaFunction = new JsiiProject({ // Or a custom Lambda project type
  parent: parent,
  name: 'my-lambda-function',
  // ... Lambda specific configurations
});

parent.synth();

Running npx projen at the root of this structure would then generate the configurations for both my-cdk-app and my-lambda-function based on their definitions within the parent. This is a game-changer for maintaining consistency across an organization’s infrastructure code.

A common pitfall is forgetting to run npx projen after making changes to .projenrc.js. Projen only updates the generated files when you explicitly run the command. This is by design, preventing accidental overwrites and giving you control over when configuration changes are applied.

The next step in mastering projen is understanding how to create custom project types or extend existing ones to encapsulate your organization’s specific development standards and CDK patterns.

Want structured learning?

Take the full Cdk course →