This is a template repository for Node.js projects. It provides a preconfigured common set of tooling and is the starting point for any new Node.js projects we build. Using this template will save you time and also promote a consistent developer experience across repositories.
It's primarily geared towards Node.js + TypeScript + AWS. If this sounds good to you keep on reading!
- TypeScript for development
- AWS CDK and AWS Constructs for infrastructure as code
- Jest for testing
- GitHub Actions for CICD
- Lerna for monorepo management
- ESLint for linting
- Prettier for formatting
- Renovate for automated dependency updates
- Lint-staged to lint files before they're committed
- Hygen for generating new packages with a consistent structure
- Automated versioning and changelog generation via conventional commits
- Strict Node & NPM versioning to match the latest AWS Lambda runtimes
- Common dev dependencies preinstalled at root level to avoid duplication across packages
- PR templates for consistency
There are a few conventions when using this repo to be aware of:
-
The CDK context values
ENVIRONMENT
andCUSTOMER
declared via the CICD CLI are as follows:ENVIRONMENT
-dev
,stage
orprod
CUSTOMER
- a string representing the end client of your software. This library is built with a SaaS mindset, where each customer can have their own configuration. If this doesn't apply simply use your own business name
-
Your
cdk.context.json
file should adopt a structure of:
{
"cuckoo": { // <--- customer(s)
"prod": { // <--- environment(s)
"account": "123456789", // <--- option(s)
"region": "eu-west-1"
}
}
}
Where a more complete example might look something like:
{
"cuckoo": {
"dev": {
"logLevel": "debug",
"account": "123456789",
"region": "eu-west-1"
},
"prod": {
"logLevel": "info",
"account": "123456789",
"region": "eu-west-1"
}
},
"acme": {
"dev": {
"logLevel": "info",
"account": "987654321",
"region": "us-east-1",
"alarmNotificationsTopic": "acme-sns-topic-dev",
"yourCustomOptions": "foo"
},
"prod": {
"logLevel": "error",
"account": "987654321",
"region": "us-east-1",
"alarmNotificationsTopic": "acme-sns-topic-prod",
"yourCustomOptions": "bar"
}
}
}
This template will automatically create releases & changelogs when new code is merged in. It does this via a bot account that you supply to it and will need to created beforehand.
These secrets will need to be made available to the repo you clone from this template:
SERVICE_ACCOUNT_TOKEN
- a GitHub access token for a bot account you own (see above)AWS_ACCESS_KEY_ID
- credentials that can be used to deploy resources into your chosen AWS accountAWS_SECRET_ACCESS_KEY
- credentials that can be used to deploy resources into chosen your AWS account
- Create a new repository for your project from this template by following these steps
- Substitute placeholder variables in the following files:
./package.json
here./infra/constants.js
here and here./cdk.context.json
multiple here./.github/workflows/version-changelog.yml
- replace "bot" with your bot account name (see prerequisites) here./.github/workflows/deploy-*
- replace "cuckoo" with your list of customers (see preamble) here, here and here
- Change the following GitHub repository settings:
- Settings > General > Pull Requests - Enable
Automatically delete head branches
- Settings > General > Pull Requests - Enable
Allow squash merging
+Default to pull request title
(disable other options) - Settings > Branches - Add a rule for
main
:- Enable
Require a pull request before merging
- Enable
Require approvals
and set to 1 - Enable
Allow specified actors to bypass required pull requests
and add your bot user (see prerequisites)
- Enable
- Settings > General > Pull Requests - Enable
- Register your new repository with Renovate via these steps
- Familiarise yourself with the structure of the package templates (see here and here)
- When you're ready to start building your application, add a new package via
generate
script
Happy coding! π
The following scripts are available from the root level of this repository:
install
- install all dependencies across the entire repobuild
- runs the typescript compiler against each monorepo packageclean
- removes all monorepo package dependencies and build fileslint
- runs the linter on the entire repo and automatically fixing errors wherever possiblelint:nofix
- same aslint
but does not automatically fix errors (faster)*test
- runs all unit and infrastructure teststest:integration:dev
- runs all integration tests against the development environmenttest:integration:stage
- runs all integration tests against the stage environmentpackage
- prepares each package for deployment to AWS (removes dev dependencies)*generate
- initialises a new monorepo package with our standard directory structure- available package types are:
cdk-package
- creates a vanilla nodejs cdk application packagegraphql
- creates a graphql server (appsync) cdk application package
- choose which you want via arguments
-- "<generator>" "<name>" "new"
- eg:
npm run generate -- "graphql" "new"
- eg:
- available package types are:
* primarily used by CICD
Below are some conventions this repository follows that are good to be aware of:
- This repository will provision 3 environments for you (dev/stage/prod)
- The development environment is automatically redeployed to whenever a new commit is pushed to an open PR
- This means the development environment is volatile. Meaning any push by another dev (or bot) will deploy over your changes
- Given our team size this volatility is not usually an issue
- The stage environment is to be treated as production-like
- The intention is that each package represents a small collection of resources that work together to fulfill some requirement
- This means it should be neither a single lambda or an entire application (somewhere inbetween)
- Generally speaking this strikes a nice balance between boilerplate & complexity
- Each package should be self-contained and not import files outside its own directory structure (eg: files from the repo root level or other packages)
There are three flavours of tests we feel give us confidence our code is operating as expected:
- Unit tests
- Integration tests
- Infrastructure tests
Our definition of each is as follows:
Unit tests should be written to validate (lower level) individual functions. They are not typically suited to testing combined units of logic, such as entire lambdas, but rather the individual functions that make up the logic within one. Following this guidance should mean mocking can be kept to a minimum and that they're relatively inexpensive to write and maintain.
Integration tests should be written to give confidence that deployed resources are functioning together as a combined unit. This means operating against real resources in the cloud in a black box fashion, ie: given a starting point, does stuff pop out at the other end as intended.
These tests are typically the most expensive to write and maintain, but also give the most confidence that stuff is actually working as the product evolves over time. Because of the complexity involved, usually only core functionality is tested in this way.
Because we use the CDK and TypeScript to manage infrastructure, it's possible to have complex logic regarding the creation or configuration of resources. Infrastructure tests are intended to validate any such complexity. Not all packages will necessarily have tests of this type.
Outlined below is the standardised directory structures for each type of package template supported:
.
βββ _templates (hygen templates for new packages)
βββ infra (repo-level CDK code)
βββ packages
Β Β βββ <package-name>
Β Β βββ infra (package-level CDK code)
Β Β βββ src (application code)
Β Β β βββ types.ts (shared typescript definitions)
Β Β βββ test (test code)
Β Β Β Β βββ infra (infrastructure tests)
Β Β Β Β βββ integration (integration tests)
Β Β Β Β βββ unit (unit tests)
This package follows the cdk-package
directory structure (see above) with some additional conventions within src
as outlined below:
.
βββ src
Β Β βββ core (directory containing any shared logic *agnostic* of API type)
Β Β βΒ Β βββ <operation-name> (directory containing logic for one type of operation)
Β Β βΒ Β
Β Β βββ graphql (directory containing any *GraphQL* API specific logic)
Β Β βΒ Β βββ schema.graphql (schema for entire graphql service)
Β Β βΒ Β βββ schema.ts (typescript definitions automaticaly generated from schema.graphql)
Β Β βΒ Β βββ xray.ts (import this file into your resolvers to automatically trace HTTP requests with AWS X-Ray)
Β Β βΒ Β βββ <operation-name> (directory containing logic for one type of operation)
Β Β βΒ Β Β Β βββ index.ts (entry-point resolver for operation)
Β Β βΒ Β Β Β βββ filter-by.ts (logic for filtering graphql responses)
Β Β βΒ Β Β Β βββ order-by.ts (logic for ordering graphql responses)
Β Β βΒ Β
Β Β βββ rest (directory containing any *RESTful* API specific logic)
Β Β βΒ Β
Β Β βββ ??? (other directories for API types supported in the future)
Amir Sekhavati π» |
Elliot Massey π» |
Julian Inwood π» |
Luke Swift π» |
Ben Parnell π» |
Alex Fenton π» |