Skip to main content

How to simplify your next software project set-up

You are about to start a new software project. What do you do?

Chances are that you create a new (git, subversion) repository, and copy various configurations, scripts and other things from similar projects that were useful. 

Maybe your company has some template repositories to start from?

Either way, setting up a new project can be time-consuming. You need some CICD pipeline configuration, build and test scripts, linter setup. There are many things that are useful and add quality to the developer workflow, but require time and work to get right.

I have done this countless times, and even if I sometimes could get up to speed fairly quickly, there was always some additional tweaking needed – and trying to figure out the details of a specific tool configuration which I have not touched for at least six months.

These issues were the reason that a tool called Projen piqued my interest.

Projen

Projen is a scaffolding tool that uses programming language code to generate different configurations for your project. There are several tools that do that. It does not just generate the initial setup and let you figure out the rest for yourself, though.

Instead, it actively maintains the various project tool configurations you use throughout the life cycle of the project. It even makes configuration files read-only to emphasize that you should not touch these files directly.

 It uses code to make a simplified, opinionated, and typed interface for various types of software projects. From this code, it generates all the various configuration files, scripts, etc that it supports. Most times it comes with some sane defaults, so you kind of get batteries included experience.

In my work, I do a lot of work with the AWS Cloud Development Kit (AWS CDK). It was through this context that I discovered Projen. The creator of Projen, Elad Ben-Israel, is one of the key developers of AWS CDK.  He described it as “CDK for software projects”. 

 

 

Here is a video where Elad introduces Projen from last year.

I did not think so much of it initially, but when I tried it out earlier this year, I got hooked. 

When I first created a new AWS CDK project with it, it added many bells & whistles, a sane project structure, and a single fairly simple file with which I configured everything.

But enough talk, let us get practical!

Set up a project with Projen

Projen itself is written in Typescript, and you will need to have Node.js installed on your computer to run it. If you already have Node.js installed on your computer, then that is great! If you do not have Node.js, you can download the Node.js runtime from the Node.js website. I would recommend to use the current long-term support of Node.js, at the time of writing that is version 14.

You can also install Node.js via different package managers, depending on the computer environment you are working with. See the Node.js package manager page for suggestions. You can also download tools there etc manage multiple separate versions of Node.js, should you need that.

Depending on what kind of project you set up, you may need additional software installed. I am going to show two examples:

  • An AWS CDK app project, using Typescript
  • A Java project

To see the different project types you have available in Projen itself, you can just run the command

npx projen new

It will show a list of project types, like this:

❯ npx projen new
npx: installed 64 in 3.622s
projen new [PROJECT-TYPE-NAME] [OPTIONS]

Creates a new Projen project

Commands:
  projen new awscdk-app-ts     AWS CDK app in TypeScript.
  projen new awscdk-construct  AWS CDK construct library project.
  projen new cdk8s-app-ts      CDK8s app in TypeScript.
  projen new cdk8s-construct   CDK8s construct library project.
  projen new cdktf-construct   CDKTF construct library project.
  projen new java              Java project.
  projen new jsii              Multi-language jsii library project.
  projen new nextjs            Next.js project without TypeScript.
  projen new nextjs-ts         Next.js project with TypeScript.
  projen new node              Node.js project.
  projen new project           Base project.
  projen new python            Python project.
  projen new react             React project without TypeScript.
  projen new react-ts          React project with TypeScript.
  projen new typescript        TypeScript project.
  projen new typescript-app    TypeScript app.

The command npx is part of the Node.js runtime installation. It will perform a temporary installation of Projen to run it. 

We are going to use the project type options awscdk-app-ts and java.

Set up an AWS CDK Typescript project

To set up the AWS CDK App project, we run

npx projen new awscdk-app-ts

 and get the following result:

❯ npx projen new awscdk-app-ts
npx: installed 64 in 3.027s
✨ Project definition file was created at /Users/erikl/Documents/Dev/repos/my-awscdk-app/.projenrc.js
✨ Synthesizing project...
🤖 yarn install --check-files
yarn install v1.22.11
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 30.42s.
✨ Synthesis complete
🤖 git init
Initialized empty Git repository in /Users/erikl/Documents/Dev/repos/my-awscdk-app/.git/
🤖 git add .
🤖 git commit --allow-empty -m "chore: project created with projen"
[master (root-commit) c14eb25] chore: project created with projen
 21 files changed, 7226 insertions(+)
 create mode 100644 .eslintrc.json
 create mode 100644 .gitattributes
 create mode 100644 .github/pull_request_template.md
 create mode 100644 .github/workflows/build.yml
 create mode 100644 .github/workflows/stale.yml
 create mode 100644 .github/workflows/upgrade.yml
 create mode 100644 .gitignore
 create mode 100644 .mergify.yml
 create mode 100644 .npmignore
 create mode 100644 .projen/deps.json
 create mode 100644 .projen/tasks.json
 create mode 100644 .projenrc.js
 create mode 100644 LICENSE
 create mode 100644 README.md
 create mode 100644 cdk.json
 create mode 100644 package.json
 create mode 100644 src/main.ts
 create mode 100644 test/main.test.ts
 create mode 100644 tsconfig.dev.json
 create mode 100644 tsconfig.json
 create mode 100644 yarn.lock
🤖 git branch -M main

❯ 

There are a few things that happened here. The key file here is the file .projenrc.js. This is a Javascript file, which is the place where you handle all the project settings for your project. The default here is to use Javascript, but other languages and formats are supported as well, including Typescript, JSON, Python, Java, etc. You should be able to use a language/format that makes sense to you.

Other bits and pieces you may see here include initialization of a Git repository, Github workflows for dependency management, builds, pull request handling, Typescript configurations, with ESlint setup, ignore files, CDK setup in place. It also defaults to the Apache 2 open source license file, which you can change to another license, or disable completely.

This project setup defaults to using Yarn as a package manager, which I already had installed. If you prefer NPM instead, that is fine – change the configuration in .projenrc.js and it will happily use NPM instead.

Let us have a brief look at .projenrc.js itself:

const { AwsCdkTypeScriptApp } = require('projen');
const project = new AwsCdkTypeScriptApp({
  cdkVersion: '1.95.2',
  defaultReleaseBranch: 'main',
  name: 'my-awscdk-app',

  // cdkDependencies: undefined,  /* Which AWS CDK modules (those that start with "@aws-cdk/") this app uses. */
  // deps: [],                    /* Runtime dependencies of this module. */
  // description: undefined,      /* The description is just a string that helps people understand the purpose of the package. */
  // devDeps: [],                 /* Build dependencies for this module. */
  // packageName: undefined,      /* The "name" in package.json. */
  // release: undefined,          /* Add release management to this project. */
});
project.synth();

This is a piece of Javascript code that creates an object of type AwsCdkTypeScriptApp, and passes some options to it. There are plenty of options to set, but most of them are optional. If you have worked with AWS CDK in the past, you can see that it includes a specific field to define which version to use. This provides a single place to set what version you want to use, instead of updating a potentially long list of dependencies, whenever you want to upgrade. 

The code statement projen.synth() at the end of the file causes the project setup to update. If you change the settings here, you process these by simply running:

npx projen

This will update any settings you have changed in .projenrc.js.

A project set up with Projen also includes several commands you can perform, which depend on the project type. You can see the list of commands available by running the command

npx projen —help.
❯ npx projen --help
projen [command]

Commands:
  projen new [PROJECT-TYPE-NAME] [OPTIONS]  Creates a new projen project
  projen clobber                            hard resets to HEAD of origin and cleans the local repo
  projen compile                            Only compile
  projen test                               Run tests
  projen build                              Full release build (test+compile)
  projen test:watch                         Run jest in watch mode
  projen test:update                        Update jest snapshots
  projen default
  projen watch                              Watch & compile in the background
  projen eslint                             Runs eslint against the codebase
  projen synth                              Synthesizes your cdk app into cdk.out (part of "yarn
                                            build")
  projen deploy                             Deploys your CDK app to the AWS cloud
  projen destroy                            Destroys your cdk app in the AWS cloud
  projen diff                               Diffs the currently deployed app against your code
  projen upgrade                            upgrade dependencies
  projen upgrade-projen                     upgrade projen
  projen completion                         generate completion script

You can also define your own project commands in .projenrc.js.

A similar article that covers using Projen with AWS CDK is How to simplify project setup with Projen.

Let us move on to set up a Python project.

Set up a Java project

The java project type sets up a generic Java project. We can set that up with npx projen new java:

❯ npx projen new java
npx: installed 64 in 2.923s
✨ Project definition file was created at /Users/erikl/Documents/Dev/repos/my-java-project/src/test/java/projenrc.java
✨ Synthesizing project...
✨ Synthesis complete
🤖 git init
Initialized empty Git repository in /Users/erikl/Documents/Dev/repos/my-java-project/.git/
🤖 git add .
🤖 git commit --allow-empty -m "chore: project created with projen"
[master (root-commit) 527cc13] chore: project created with projen
 10 files changed, 415 insertions(+)
 create mode 100644 .gitattributes
 create mode 100644 .github/workflows/stale.yml
 create mode 100644 .gitignore
 create mode 100644 .projen/deps.json
 create mode 100644 .projen/tasks.json
 create mode 100644 README.md
 create mode 100644 pom.xml
 create mode 100644 src/main/java/org/acme/Main.java
 create mode 100644 src/test/java/org/acme/MyTest.java
 create mode 100644 src/test/java/projenrc.java
🤖 git branch -M main

❯

Similar to the AWS CDK Typescript app project, Projen sets up a Git repository, adds Github workflows, adds ignore file. It also adds a Maven project file (pom.xml) and some sample code. Notice here that the Projen configuration is now a Java file, in

src/test/java/projenrc.java.

A brief look into this file we see this setup:

import io.github.cdklabs.projen.java.JavaProject;
import io.github.cdklabs.projen.java.JavaProjectOptions;

public class projenrc {
    public static void main(String[] args) {
        JavaProject project = new JavaProject(JavaProjectOptions.builder()
            .artifactId("my-app")
            .groupId("org.acme")
            .name("my-java-project")
            .version("0.1.0")
            .build());
        project.synth();
    }
}

The principle is the same as for the AWS CDK Typescript project, with just different syntax. You run

npx projen

 whenever you have done an update in projenrc.java, and it updates the settings.

Set project defaults

If you are not happy with the defaults provided for a project type, you can change them in the file, or even directly from the command line. To see what options to can change from the command line, you can run the —help option for the project type, for example:

❯ npx projen new java --help        
npx: installed 64 in 3.094s
projen new java

Java project.

Required:
      --artifact-id  The artifactId is generally the name that the project is known by
                                                             [string] [required] [default: "my-app"]
      --group-id     This is generally unique amongst an organization or a project
                                                           [string] [required] [default: "org.acme"]
      --name         This is the name of your project
                                                    [string] [required] [default: "my-java-project"]
      --version      This is the last piece of the naming puzzle
                                                              [string] [required] [default: "0.1.0"]

Optional:
      --clobber              Add a `clobber` task which resets the repo to origin [default: true]
                                                                                           [boolean]
      --description          Description of a project is always good                        [string]
      --dev-container        Add a VSCode development environment (used for GitHub Codespaces)
                             [default: false]                                              [boolean]
      --distdir              Final artifact output directory [default: "dist/java"]         [string]
      --github               Enable GitHub integration [default: true]                     [boolean]
      --gitpod               Add a Gitpod development environment [default: false]         [boolean]
      --junit                Include junit tests [default: true]                           [boolean]
      --mergify              Whether mergify should be enabled on this repository or not [default:
                             true]                                                         [boolean]
      --outdir               The root directory of the project [default: "."]               [string]
      --packaging            Project packaging format [default: "jar"]                      [string]
      --project-type         Which type of project this is (library/app) [default:
                             ProjectType.UNKNOWN]                                           [string]
      --projenrc-java        Use projenrc in java [default: true]                          [boolean]
      --projenrc-json        Generate (once) .projenrc.json (in JSON). Set to `false` in order to
                             disable .projenrc.json generation [default: false]            [boolean]
      --sample               Include sample code and test if the relevant directories don't exist
                                                                                           [boolean]
      --sample-java-package  The java package to use for the code sample [default: "org.acme"]
                                                                                            [string]
      --stale                Auto-close of stale issues and pull request [default: true]   [boolean]
      --url                  The URL, like the name, is not required                        [string]
      --vscode               Enable VSCode integration [default: true]                     [boolean]

Positionals:
  PROJECT-TYPE-NAME  optional only when --from is used and there is a single project type in the
                     external module                                                        [string]

Options:
      --post      Run post-synthesis steps such as installing dependencies. Use --no-post to skip
                                                                           [boolean] [default: true]
  -w, --watch     Keep running and resynthesize when projenrc changes     [boolean] [default: false]
      --debug     Debug logs                                              [boolean] [default: false]
      --rc        path to .projenrc.js file
                 [string] [default: "/Users/erikl/Documents/Dev/repos/my-java-project/.projenrc.js"]
      --help      Show help                                                                [boolean]
      --synth     Synthesize after creating .projenrc.js                   [boolean] [default: true]
      --comments  Include commented out options in .projenrc.js (use --no-comments to disable)
                                                                           [boolean] [default: true]
  -f, --from      External jsii npm module to create project from. Supports any package spec
                  supported by npm (such as "my-pack@^2.0")                                 [string]
      --git       Run `git init` and create an initial commit (use --no-git to disable)
                                                                           [boolean] [default: true]

❯ 

Final thoughts

I really like Projen, and it has been quite helpful, I think. But to be honest, some areas are more fleshed out than others and my primary use case is for AWS CDK-based solutions. 

The tool is also still in a 0.x release, so the interface may change from time to time. There are frequent updates, so check the Github repository for the latest updates.

You can also define your own project types and use them besides the bundled project types. This area is out of scope for this article, but you can, for example, read more about that here.

Try it out and see what you think!

Erik Lundevall-Zara

076-100 71 68

“Enable ideas, challenge the present, never stop learning and always be nice.“