@ackee/create-node-app 1.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORS +2 -1
- package/README.md +27 -19
- package/docs/development.md +42 -0
- package/lib/Bootstrap.js +106 -65
- package/lib/Bootstrap.js.map +1 -1
- package/lib/Builder.js +111 -0
- package/lib/Builder.js.map +1 -0
- package/lib/Files.js +21 -0
- package/lib/Files.js.map +1 -0
- package/lib/Logger.js +26 -8
- package/lib/Logger.js.map +1 -1
- package/lib/Mergers/ConfigMerger.js +22 -0
- package/lib/Mergers/ConfigMerger.js.map +1 -0
- package/lib/Mergers/ContainerMerger.js +172 -0
- package/lib/Mergers/ContainerMerger.js.map +1 -0
- package/lib/Mergers/EnvJsoncMerger.js +20 -0
- package/lib/Mergers/EnvJsoncMerger.js.map +1 -0
- package/lib/Mergers/Merger.js +36 -0
- package/lib/Mergers/Merger.js.map +1 -0
- package/lib/Mergers/PackageJsonMerger.js +36 -0
- package/lib/Mergers/PackageJsonMerger.js.map +1 -0
- package/lib/Npm.js +40 -12
- package/lib/Npm.js.map +1 -1
- package/lib/PackageJson.js +4 -4
- package/lib/PackageJson.js.map +1 -1
- package/lib/StarterLoader.js +86 -0
- package/lib/StarterLoader.js.map +1 -0
- package/package.json +10 -7
- package/src/Bootstrap.ts +123 -82
- package/src/Builder.ts +172 -0
- package/src/Files.ts +22 -0
- package/src/Logger.ts +26 -7
- package/src/Mergers/ConfigMerger.ts +28 -0
- package/src/Mergers/ContainerMerger.ts +241 -0
- package/src/Mergers/EnvJsoncMerger.ts +24 -0
- package/src/Mergers/Merger.ts +51 -0
- package/src/Mergers/PackageJsonMerger.ts +45 -0
- package/src/Npm.ts +60 -15
- package/src/PackageJson.ts +6 -4
- package/src/Starter.ts +2 -2
- package/src/StarterLoader.ts +148 -0
- package/starter/{cloudrun → _base}/.env.jsonc +1 -5
- package/starter/{cloudrun → _base}/.eslintrc.cjs +3 -2
- package/starter/_base/README.md +53 -0
- package/starter/_base/package.json +45 -0
- package/starter/{cloudrun → _base}/src/adapters/pino.logger.ts +1 -1
- package/starter/_base/src/config.ts +16 -0
- package/starter/{cloudrun-graphql → _base}/src/container.ts +3 -1
- package/starter/_base/src/index.ts +14 -0
- package/starter/{cloudrun → _base}/src/view/cli/README.md +2 -6
- package/starter/api/graphql/.env.jsonc +8 -0
- package/starter/{cloudrun-graphql → api/graphql}/.eslintrc.cjs +4 -5
- package/starter/api/graphql/node-app.jsonc +6 -0
- package/starter/api/graphql/package.json +36 -0
- package/starter/{cloudrun-graphql → api/graphql}/src/config.ts +0 -4
- package/starter/api/graphql/src/index.ts +11 -0
- package/starter/{cloudrun-graphql → api/graphql}/src/test/helloWorld.test.ts +14 -3
- package/starter/api/graphql/src/view/graphql/context-factory.ts +13 -0
- package/starter/{cloudrun-graphql → api/graphql}/src/view/server.ts +16 -6
- package/starter/api/rest/.env.jsonc +6 -0
- package/starter/api/rest/.eslintrc.cjs +8 -0
- package/starter/api/rest/node-app.jsonc +6 -0
- package/starter/api/rest/package.json +25 -0
- package/starter/{cloudrun → api/rest}/src/config.ts +0 -5
- package/starter/api/rest/src/container.ts +13 -0
- package/starter/{cloudrun → api/rest}/src/index.ts +4 -4
- package/starter/{cloudrun → api/rest}/src/test/health-check.test.ts +3 -5
- package/starter/{cloudrun → api/rest}/src/test/util/openapi-test.util.ts +3 -3
- package/starter/{cloudrun → api/rest}/src/view/rest/middleware/error-handler.ts +1 -1
- package/starter/{cloudrun → api/rest}/src/view/rest/routes.ts +1 -1
- package/starter/{cloudrun → api/rest}/src/view/rest/util/openapi.util.ts +22 -19
- package/starter/{cloudrun → api/rest}/src/view/server.ts +6 -4
- package/starter/infra/postgresql-knex/.env.jsonc +5 -0
- package/starter/{shared → infra/postgresql-knex}/docker-compose/docker-compose.yml +1 -1
- package/starter/infra/postgresql-knex/knexfile.ts +16 -0
- package/starter/infra/postgresql-knex/node-app.jsonc +6 -0
- package/starter/infra/postgresql-knex/package.json +13 -0
- package/starter/infra/postgresql-knex/src/adapters/knex.database.test.ts +21 -0
- package/starter/infra/postgresql-knex/src/adapters/knex.database.ts +14 -0
- package/starter/infra/postgresql-knex/src/adapters/repositories/migration.repository.ts +24 -0
- package/starter/infra/postgresql-knex/src/config.ts +14 -0
- package/starter/infra/postgresql-knex/src/container.ts +23 -0
- package/starter/infra/postgresql-knex/src/db/migration.template.ts +4 -0
- package/starter/infra/postgresql-knex/src/db/migrations/.gitkeep +0 -0
- package/starter/infra/postgresql-knex/src/db/seed.template.ts +3 -0
- package/starter/infra/postgresql-knex/src/db/seeds/.gitkeep +0 -0
- package/starter/infra/postgresql-knex/src/domain/ports/database.d.ts +4 -0
- package/starter/infra/postgresql-knex/src/domain/ports/repositories/migration.repository.d.ts +9 -0
- package/starter/infra/postgresql-knex/src/test/setup.ts +16 -0
- package/starter/{shared → pipeline/cloudrun-gitlab}/.gitlab-ci.yml +15 -6
- package/starter/pipeline/cloudrun-gitlab/node-app.jsonc +6 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/Toolbelt.js +0 -102
- package/lib/Toolbelt.js.map +0 -1
- package/lib/cloudrun/CloudRunStarter.js +0 -127
- package/lib/cloudrun/CloudRunStarter.js.map +0 -1
- package/lib/cloudrun-graphql/GraphQLStarter.js +0 -118
- package/lib/cloudrun-graphql/GraphQLStarter.js.map +0 -1
- package/src/Toolbelt.ts +0 -132
- package/src/cloudrun/CloudRunStarter.ts +0 -182
- package/src/cloudrun-graphql/GraphQLStarter.ts +0 -182
- package/starter/cloudrun/README.md +0 -69
- package/starter/cloudrun/src/container.ts +0 -18
- package/starter/cloudrun/src/context.ts +0 -39
- package/starter/cloudrun/src/domain/errors/codes.ts +0 -9
- package/starter/cloudrun/src/domain/errors/errors.ts +0 -25
- package/starter/cloudrun/src/domain/ports/logger.d.ts +0 -21
- package/starter/cloudrun-graphql/.env.jsonc +0 -12
- package/starter/cloudrun-graphql/README.md +0 -53
- package/starter/cloudrun-graphql/src/adapters/pino.logger.ts +0 -44
- package/starter/cloudrun-graphql/src/index.ts +0 -11
- package/starter/shared/.gitignore_ +0 -5
- package/starter/shared/ci-branch-config/common.env +0 -7
- package/starter/shared/ci-branch-config/development.env +0 -7
- package/starter/shared/ci-branch-config/master.env +0 -7
- package/starter/shared/ci-branch-config/stage.env +0 -7
- package/starter/shared/docker-compose/docker-compose.override.yml +0 -5
- package/starter/shared/jest.config.js +0 -12
- /package/starter/{shared → _base}/.dockerignore +0 -0
- /package/starter/{cloudrun → _base}/.eslint.tsconfig.json +0 -0
- /package/starter/{shared → _base}/.mocha-junit-config.json +0 -0
- /package/starter/{shared → _base}/.mocharc.json +0 -0
- /package/starter/{shared → _base}/.nvmrc +0 -0
- /package/starter/{shared → _base}/Dockerfile +0 -0
- /package/starter/{shared → _base}/prettier.config.cjs +0 -0
- /package/starter/{cloudrun-graphql → _base}/src/context.ts +0 -0
- /package/starter/{cloudrun-graphql → _base}/src/domain/errors/codes.ts +0 -0
- /package/starter/{cloudrun-graphql → _base}/src/domain/errors/errors.ts +0 -0
- /package/starter/{cloudrun-graphql → _base}/src/domain/ports/logger.d.ts +0 -0
- /package/starter/{shared → _base}/src/test/setup.ts +0 -0
- /package/starter/{cloudrun → _base}/src/view/cli/cli.ts +0 -0
- /package/starter/{shared → _base}/tsconfig.json +0 -0
- /package/starter/{cloudrun-graphql → api/graphql}/.eslint.tsconfig.json +0 -0
- /package/starter/{cloudrun-graphql → api/graphql}/codegen.yml +0 -0
- /package/starter/{cloudrun-graphql → api/graphql}/src/view/controller.ts +0 -0
- /package/starter/{cloudrun-graphql → api/graphql}/src/view/graphql/resolvers/greeting.resolver.ts +0 -0
- /package/starter/{cloudrun-graphql → api/graphql}/src/view/graphql/resolvers.ts +0 -0
- /package/starter/{cloudrun-graphql → api/graphql}/src/view/graphql/schema/schema.graphql +0 -0
- /package/starter/{cloudrun-graphql → api/graphql}/src/view/graphql/schema.ts +0 -0
- /package/starter/{cloudrun → api/rest}/src/domain/health-check.service.ts +0 -0
- /package/starter/{cloudrun → api/rest}/src/view/cli/openapi/generate.ts +0 -0
- /package/starter/{cloudrun/src/view/rest/controller → api/rest/src/view/rest/controllers}/health-check.controller.ts +0 -0
- /package/starter/{cloudrun → api/rest}/src/view/rest/middleware/context-middleware.ts +0 -0
- /package/starter/{cloudrun → api/rest}/src/view/rest/middleware/request-logger.ts +0 -0
- /package/starter/{cloudrun → api/rest}/src/view/rest/request.d.ts +0 -0
- /package/starter/{cloudrun → api/rest}/src/view/rest/spec/openapi.yml +0 -0
- /package/starter/{shared → infra/postgresql-knex}/docker-compose/docker-compose-entrypoint.sh +0 -0
- /package/starter/{shared → infra/postgresql-knex}/docker-compose/docker-compose.ci.yml +0 -0
- /package/starter/{shared → infra/postgresql-knex}/docker-compose/docker-compose.local.yml +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Create-Node-App Project
|
|
2
|
+
|
|
3
|
+
Node.js application bootstrapped with [create-node-app](https://github.com/AckeeCZ/create-node-app)
|
|
4
|
+
|
|
5
|
+
## 🏗️ Architecture
|
|
6
|
+
|
|
7
|
+
All domain logic should live inside the [domain folder](src/domain) and should be accessed only via [container](src/container.ts). Each external dependency should be referenced by its own port inside the [domain/port](src/domain/ports) folder. These ports are also part of the container and can be implemented by any external dependency inside the [adapters](src/adapters/) folder.
|
|
8
|
+
|
|
9
|
+
The application should be accessed through the [view](src/view/) layer of the application. All UI and interfaces should be defined there. The main entrypoint is defined in the [index.ts](src/index.ts) file.
|
|
10
|
+
|
|
11
|
+
Each domain service should always accept [RequestContext](src/context.ts) as the first parameter. Part of the context is also the container with all of the application dependencies.
|
|
12
|
+
|
|
13
|
+
## 👷 Development
|
|
14
|
+
|
|
15
|
+
Application configuration is handled by [Configuru](https://github.com/AckeeCZ/configuru). All env options that can be changed or need to be set up are described in [.env.jsonc](.env.jsonc).
|
|
16
|
+
|
|
17
|
+
The whole codebase is checked and improved using lint and prettier, run `lint:fix` and `prettier:fix` before committing changes to git.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm run lint:fix
|
|
21
|
+
npm run prettier:fix
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## ✅ Tests
|
|
25
|
+
|
|
26
|
+
Test are written using [mocha](https://github.com/mochajs/mocha). They are maintained in two ways:
|
|
27
|
+
|
|
28
|
+
- **unit tests** should be next to the tested file with `test.ts` suffix,
|
|
29
|
+
- **integration tests** should be located inside the [test](src/test/) folder.
|
|
30
|
+
|
|
31
|
+
Use mocked container during integration tests.
|
|
32
|
+
|
|
33
|
+
If you need any prerequisites during tests, use the [setup.ts](src/test/setup.ts) file.
|
|
34
|
+
|
|
35
|
+
To run tests use the test command:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm run test
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 🚀 Quick start
|
|
42
|
+
|
|
43
|
+
1. Build the code
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm run build
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
2. Start the entrypoint / server
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm run start
|
|
53
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "mocha",
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"start": "node -r source-map-support/register dist/index.js",
|
|
9
|
+
"ci-test:no-coverage": "npm run test -- --parallel=false -R mocha-multi-reporters --reporter-options configFile=.mocha-junit-config.json",
|
|
10
|
+
"ci-test": "nyc -a -r cobertura --report-dir output npm run ci-test:no-coverage",
|
|
11
|
+
"prettier": "prettier --check --write '**/*.{ts,js,json,jsonc,md}'",
|
|
12
|
+
"lint": "eslint '**/*.ts' -f codeframe --fix",
|
|
13
|
+
"codestyle": "npm run lint && npm run prettier",
|
|
14
|
+
"ci-lint": "npm run lint -- -f checkstyle -o ./output/checkstyle-result.xml",
|
|
15
|
+
"cli": "tsx ./src/view/cli/cli.ts"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"description": "",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"configuru": "^1.0.0",
|
|
23
|
+
"pino": "^9.0.0",
|
|
24
|
+
"source-map-support": "^0.5.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@ackee/styleguide-backend-config": "^1.0.1",
|
|
28
|
+
"@istanbuljs/nyc-config-typescript": "^1.0.0",
|
|
29
|
+
"@types/mocha": "^10.0.0",
|
|
30
|
+
"@types/node": "^24.0.0",
|
|
31
|
+
"eslint": "^8.0.0",
|
|
32
|
+
"eslint-formatter-gitlab": "^5.0.0",
|
|
33
|
+
"mocha": "^11.0.0",
|
|
34
|
+
"mocha-junit-reporter": "^2.0.0",
|
|
35
|
+
"mocha-multi-reporters": "^1.0.0",
|
|
36
|
+
"nyc": "^17.0.0",
|
|
37
|
+
"pino-pretty": "^13.0.0",
|
|
38
|
+
"prettier": "^3.0.0",
|
|
39
|
+
"ts-node": "^10.0.0",
|
|
40
|
+
"tsx": "^4.0.0",
|
|
41
|
+
"typescript": "^5.0.0",
|
|
42
|
+
"yargs": "^18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"type": "module"
|
|
45
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createLoader, maskedValues, values } from 'configuru'
|
|
2
|
+
import { Level } from 'pino'
|
|
3
|
+
|
|
4
|
+
const loader = createLoader({
|
|
5
|
+
defaultConfigPath: '.env.jsonc',
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
const configSchema = {
|
|
9
|
+
logger: {
|
|
10
|
+
defaultLevel: loader.custom(x => x as Level)('LOGGER_DEFAULT_LEVEL'),
|
|
11
|
+
pretty: loader.bool('LOGGER_PRETTY'),
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const config = values(configSchema)
|
|
16
|
+
export const safeConfig = maskedValues(configSchema)
|
|
@@ -6,7 +6,9 @@ export interface Container {
|
|
|
6
6
|
logger: LoggerPort
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export
|
|
9
|
+
export type ContainerFactory = () => Promise<Container>
|
|
10
|
+
|
|
11
|
+
export const createContainer: ContainerFactory = async () => {
|
|
10
12
|
const logger = pinoLoggerFactory.create(config.logger)
|
|
11
13
|
|
|
12
14
|
return {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContainer } from './container.js'
|
|
2
|
+
import { RequestContext } from './context.js'
|
|
3
|
+
|
|
4
|
+
const entrypoint = async () => {
|
|
5
|
+
const ctx: RequestContext = {
|
|
6
|
+
container: await createContainer(),
|
|
7
|
+
type: 'api-user',
|
|
8
|
+
user: null,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
ctx.container.logger.info('Hello World! 🎉')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
void entrypoint()
|
|
@@ -4,13 +4,9 @@ Cli tool to operate scripts that can use internal services or to add development
|
|
|
4
4
|
|
|
5
5
|
## 👷 Development
|
|
6
6
|
|
|
7
|
-
The CLI tool is build around `yargs` library. Each command needs its own `ts` file in current folder or in subfolder. The entrypoint `Cli.ts` scans the sub folders for `ts` files and intrerprets them as a command under the module called same as the folder.
|
|
7
|
+
The CLI tool is build around `yargs` library. Each command needs its own `ts` file in current folder or in subfolder. The entrypoint `Cli.ts` scans the sub folders for `ts` files and intrerprets them as a command under the module called same as the folder. Each command should export only functions and variables according to `CommandDefinition` in [cli.ts file](./cli.ts).
|
|
8
8
|
|
|
9
|
-
For example,
|
|
10
|
-
|
|
11
|
-
Each command should export only functions and variables according to `CommandDefinition` in [cli.ts file](./cli.ts).
|
|
12
|
-
|
|
13
|
-
Take a look at the [generate.ts](./openapi/generate.ts) for example how to implement own command.
|
|
9
|
+
For example, if you add `openapi` folder, it will creates a new module `openapi`. If you add a new command called `generate.ts`, it will also assign a new generate command to this module.
|
|
14
10
|
|
|
15
11
|
## ⚠️ Usage in production scripts
|
|
16
12
|
|
|
@@ -5,7 +5,7 @@ const defaultConfig = {
|
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
7
|
root: true,
|
|
8
|
-
ignorePatterns: ['dist', 'docs', '
|
|
8
|
+
ignorePatterns: ['dist', 'src/openapi', 'docs', 'knexfile.ts'],
|
|
9
9
|
overrides: [
|
|
10
10
|
{
|
|
11
11
|
...omit(defaultConfig, ['ignorePatterns']),
|
|
@@ -14,10 +14,9 @@ module.exports = {
|
|
|
14
14
|
project: '.eslint.tsconfig.json',
|
|
15
15
|
},
|
|
16
16
|
rules: {
|
|
17
|
-
...defaultConfig
|
|
18
|
-
'@typescript-eslint/
|
|
19
|
-
|
|
20
|
-
},
|
|
17
|
+
...defaultConfig.rules,
|
|
18
|
+
'@typescript-eslint/no-empty-object-type': 0,
|
|
19
|
+
}
|
|
21
20
|
},
|
|
22
21
|
{
|
|
23
22
|
files: ['**/*.graphql'],
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "app-api-graphql",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"generate:api": "graphql-codegen --config codegen.yml && npm run codestyle",
|
|
7
|
+
"build:copy-schema": "mkdir -p ./dist/view/graphql/schema && cp -r ./src/view/graphql/schema ./dist/view/graphql/schema",
|
|
8
|
+
"build": "npm run build:copy-schema && tsc"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [],
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"description": "",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@as-integrations/express5": "^1.1.2",
|
|
16
|
+
"@apollo/server": "^4.1.0",
|
|
17
|
+
"@graphql-tools/load-files": "^7.0.0",
|
|
18
|
+
"@graphql-tools/merge": "^9.0.0",
|
|
19
|
+
"@graphql-tools/schema": "^10.0.0",
|
|
20
|
+
"lodash-es": "^4.17.21",
|
|
21
|
+
"express": "^5.0.0",
|
|
22
|
+
"graphql": "^16.0.0",
|
|
23
|
+
"node-healthz": "^2.0.0",
|
|
24
|
+
"pino-http": "^10.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@graphql-codegen/cli": "^5.0.0",
|
|
28
|
+
"@graphql-codegen/typescript": "^4.0.0",
|
|
29
|
+
"@graphql-codegen/typescript-resolvers": "^4.0.0",
|
|
30
|
+
"@graphql-eslint/eslint-plugin": "^4.4.0",
|
|
31
|
+
"@types/express": "^5.0.0",
|
|
32
|
+
"@types/supertest": "^6.0.0",
|
|
33
|
+
"supertest": "^7.0.0"
|
|
34
|
+
},
|
|
35
|
+
"type": "module"
|
|
36
|
+
}
|
|
@@ -11,10 +11,6 @@ const configSchema = {
|
|
|
11
11
|
allowResponseErrors: loader.bool('SERVER_ALLOW_RESPONSE_ERRORS'),
|
|
12
12
|
enableIntrospection: loader.bool('SERVER_ENABLE_INTROSPECTION'),
|
|
13
13
|
},
|
|
14
|
-
logger: {
|
|
15
|
-
defaultLevel: loader.custom(x => x as any as Level)('LOGGER_DEFAULT_LEVEL'),
|
|
16
|
-
pretty: loader.bool('LOGGER_PRETTY'),
|
|
17
|
-
},
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
export const config = values(configSchema)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { config, safeConfig } from './config.js'
|
|
2
|
+
import { createAppServer, startServer } from './view/server.js'
|
|
3
|
+
import { pinoLoggerFactory } from './adapters/pino.logger.js'
|
|
4
|
+
import { createContainer } from './container.js'
|
|
5
|
+
|
|
6
|
+
const logger = pinoLoggerFactory.create(config.logger)
|
|
7
|
+
|
|
8
|
+
logger.info({ config: safeConfig }, 'Loaded config')
|
|
9
|
+
|
|
10
|
+
const appServer = createAppServer()
|
|
11
|
+
void startServer({ ...appServer, logger, containerFactory: createContainer })
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
|
-
import { describe,
|
|
2
|
+
import { describe, before, test } from 'mocha'
|
|
3
3
|
import { createAppServer } from '../view/server.js'
|
|
4
4
|
import { gql } from 'graphql-tag'
|
|
5
|
+
import { ApolloServer } from '@apollo/server'
|
|
5
6
|
|
|
6
7
|
describe('Hello world', () => {
|
|
7
|
-
|
|
8
|
+
let server: ApolloServer
|
|
9
|
+
|
|
10
|
+
before(async () => {
|
|
11
|
+
const { server: appServer } = createAppServer()
|
|
12
|
+
server = appServer
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
after(async () => {
|
|
16
|
+
await server.stop()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('should return greeting', async () => {
|
|
8
20
|
const query = gql`
|
|
9
21
|
query Hello {
|
|
10
22
|
greeting
|
|
11
23
|
}
|
|
12
24
|
`
|
|
13
|
-
const { server } = createAppServer()
|
|
14
25
|
const res = await server.executeOperation({ query })
|
|
15
26
|
|
|
16
27
|
assert(res.body.kind === 'single')
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { RequestContext } from '../../context.js'
|
|
2
|
+
import { ContainerFactory } from '../../container.js'
|
|
3
|
+
|
|
4
|
+
export const contextFactory = async (
|
|
5
|
+
containerFactory: ContainerFactory
|
|
6
|
+
): Promise<RequestContext> => {
|
|
7
|
+
const container = await containerFactory()
|
|
8
|
+
return {
|
|
9
|
+
type: 'api-user',
|
|
10
|
+
container,
|
|
11
|
+
user: null, // Add UserContext if Auth is implemented
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -6,7 +6,9 @@ import { schema } from './graphql/schema.js'
|
|
|
6
6
|
import express from 'express'
|
|
7
7
|
import { ctrl } from './controller.js'
|
|
8
8
|
import { expressMiddleware } from '@as-integrations/express5'
|
|
9
|
-
import {
|
|
9
|
+
import { LoggerPort } from '../domain/ports/logger.d.js'
|
|
10
|
+
import { ContainerFactory } from '../container.js'
|
|
11
|
+
import { contextFactory } from './graphql/context-factory.js'
|
|
10
12
|
|
|
11
13
|
export const clientSchema = makeExecutableSchema({
|
|
12
14
|
typeDefs: schema,
|
|
@@ -29,13 +31,21 @@ export const createAppServer = () => {
|
|
|
29
31
|
export async function startServer({
|
|
30
32
|
app,
|
|
31
33
|
server,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
logger,
|
|
35
|
+
containerFactory,
|
|
36
|
+
}: ReturnType<typeof createAppServer> & {
|
|
37
|
+
logger: LoggerPort
|
|
38
|
+
containerFactory: ContainerFactory
|
|
39
|
+
}) {
|
|
36
40
|
await server.start()
|
|
37
41
|
|
|
38
|
-
app.use(
|
|
42
|
+
app.use(
|
|
43
|
+
'/api/graphql',
|
|
44
|
+
ctrl.json,
|
|
45
|
+
expressMiddleware(server, {
|
|
46
|
+
context: () => contextFactory(containerFactory),
|
|
47
|
+
})
|
|
48
|
+
)
|
|
39
49
|
|
|
40
50
|
app.listen({ port: config.server.port }, () =>
|
|
41
51
|
logger.info({}, `Server started, port=${config.server.port}`)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "app-api-rest",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"generate:api": "npm run cli openapi generate src/view/rest/spec/openapi.yml && npm run codestyle"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [],
|
|
9
|
+
"author": "",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"description": "",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"express": "^5.0.0",
|
|
14
|
+
"node-healthz": "^2.0.0",
|
|
15
|
+
"pino-http": "^10.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/express": "^5.0.0",
|
|
19
|
+
"@types/supertest": "^6.0.0",
|
|
20
|
+
"openapi-typescript": "^7.0.0",
|
|
21
|
+
"supertest": "^7.0.0",
|
|
22
|
+
"yaml": "^2.0.0"
|
|
23
|
+
},
|
|
24
|
+
"type": "module"
|
|
25
|
+
}
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { createLoader, maskedValues, values } from 'configuru'
|
|
2
|
-
import { Level } from 'pino'
|
|
3
2
|
|
|
4
3
|
const loader = createLoader({
|
|
5
4
|
defaultConfigPath: '.env.jsonc',
|
|
6
5
|
})
|
|
7
6
|
|
|
8
7
|
const configSchema = {
|
|
9
|
-
logger: {
|
|
10
|
-
defaultLevel: loader.custom(x => x as Level)('LOGGER_DEFAULT_LEVEL'),
|
|
11
|
-
pretty: loader.bool('LOGGER_PRETTY'),
|
|
12
|
-
},
|
|
13
8
|
server: {
|
|
14
9
|
port: loader.number('SERVER_PORT'),
|
|
15
10
|
enableProductionHttpErrorResponses: loader.bool(
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { healthCheckService } from './domain/health-check.service.js'
|
|
2
|
+
|
|
3
|
+
export interface Container {
|
|
4
|
+
healthCheckService: typeof healthCheckService
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type ContainerFactory = () => Promise<Container>
|
|
8
|
+
|
|
9
|
+
export const createContainer: ContainerFactory = async () => {
|
|
10
|
+
return {
|
|
11
|
+
healthCheckService,
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { config } from './config.js'
|
|
2
|
-
import { createContainer } from './container.js'
|
|
3
2
|
import { createServer } from './view/server.js'
|
|
3
|
+
import { pinoLoggerFactory } from './adapters/pino.logger.js'
|
|
4
|
+
import { createContainer } from './container.js'
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
-
const { logger } = appContainer
|
|
6
|
+
const logger = pinoLoggerFactory.create(config.logger)
|
|
7
7
|
|
|
8
|
-
const server = createServer(
|
|
8
|
+
const server = await createServer(createContainer)
|
|
9
9
|
|
|
10
10
|
server.listen(config.server.port, () => {
|
|
11
11
|
logger.info(
|
|
@@ -5,12 +5,10 @@ import { createContainer } from '../container.js'
|
|
|
5
5
|
import { requestTyped } from './util/openapi-test.util.js'
|
|
6
6
|
|
|
7
7
|
describe('Health Check API', () => {
|
|
8
|
-
let server: ReturnType<typeof createServer
|
|
9
|
-
let container: ReturnType<typeof createContainer>
|
|
8
|
+
let server: Awaited<ReturnType<typeof createServer>>
|
|
10
9
|
|
|
11
|
-
before(() => {
|
|
12
|
-
|
|
13
|
-
server = createServer(container)
|
|
10
|
+
before(async () => {
|
|
11
|
+
server = await createServer(createContainer)
|
|
14
12
|
})
|
|
15
13
|
|
|
16
14
|
describe('GET /api/v1/healthz', () => {
|
|
@@ -21,7 +21,7 @@ export const requestTyped = <
|
|
|
21
21
|
Resource extends KeysOfUnion<openapi.paths>,
|
|
22
22
|
Method extends Exclude<KeysOfUnion<openapi.paths[Resource]>, 'parameters'>,
|
|
23
23
|
Resp extends OpenApiRouteResponseBody<Openapi[Resource], Method>,
|
|
24
|
-
Openapi extends openapi.paths = openapi.paths
|
|
24
|
+
Openapi extends openapi.paths = openapi.paths,
|
|
25
25
|
>(
|
|
26
26
|
server: Express,
|
|
27
27
|
resource: Resource,
|
|
@@ -57,10 +57,10 @@ export const requestTyped = <
|
|
|
57
57
|
) {
|
|
58
58
|
if (headers) {
|
|
59
59
|
Object.entries(headers).forEach(([key, value]) => {
|
|
60
|
-
|
|
60
|
+
req.set(key, value)
|
|
61
61
|
})
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
originalSend(body as any)
|
|
64
64
|
return enhanced
|
|
65
65
|
},
|
|
66
66
|
responseTyped: () => {
|
|
@@ -42,7 +42,7 @@ export function createErrorHandler(
|
|
|
42
42
|
return (error, _req, res, _next) => {
|
|
43
43
|
const statusCode =
|
|
44
44
|
error instanceof DomainError
|
|
45
|
-
? ERROR_DOMAIN_CODE_TO_HTTP_STATUS[error.code] ?? 500
|
|
45
|
+
? (ERROR_DOMAIN_CODE_TO_HTTP_STATUS[error.code] ?? 500)
|
|
46
46
|
: 500
|
|
47
47
|
|
|
48
48
|
;(res as any).error = errorToDevObject(error)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import express from 'express'
|
|
2
2
|
import { openApiRouter, registerOpenApiRoutes } from './util/openapi.util.js'
|
|
3
|
-
import { healthCheckController } from './
|
|
3
|
+
import { healthCheckController } from './controllers/health-check.controller.js'
|
|
4
4
|
|
|
5
5
|
const apiRouter = openApiRouter(express.Router(), {
|
|
6
6
|
removePrefix: '/api/v1',
|
|
@@ -6,7 +6,7 @@ import { RequestContext } from '../../../context.js'
|
|
|
6
6
|
export type OpenApiRouteResponseBodyMethod<
|
|
7
7
|
T,
|
|
8
8
|
TMethod extends string = 'get',
|
|
9
|
-
TDefault = unknown
|
|
9
|
+
TDefault = unknown,
|
|
10
10
|
> = T extends {
|
|
11
11
|
[key in TMethod]: {
|
|
12
12
|
responses: { 200: { content: { 'application/json': infer U } } }
|
|
@@ -17,7 +17,7 @@ export type OpenApiRouteResponseBodyMethod<
|
|
|
17
17
|
|
|
18
18
|
export type OpenApiRouteResponseBody<
|
|
19
19
|
T,
|
|
20
|
-
TMethod extends string = 'get'
|
|
20
|
+
TMethod extends string = 'get',
|
|
21
21
|
> = OpenApiRouteResponseBodyMethod<
|
|
22
22
|
T,
|
|
23
23
|
TMethod,
|
|
@@ -41,18 +41,18 @@ export type OpenApiRoutePathParam<T> = T extends {
|
|
|
41
41
|
}
|
|
42
42
|
? U
|
|
43
43
|
: T extends { post: { parameters: { path: infer U } } }
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
? U
|
|
45
|
+
: T extends { put: { parameters: { path: infer U } } }
|
|
46
|
+
? U
|
|
47
|
+
: unknown
|
|
48
48
|
|
|
49
49
|
export type OpenApiRouteQueryParam<T> = T extends {
|
|
50
50
|
get: { parameters: { query: infer U } }
|
|
51
51
|
}
|
|
52
52
|
? U
|
|
53
53
|
: T extends { post: { parameters: { query: infer U } } }
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
? U
|
|
55
|
+
: unknown
|
|
56
56
|
|
|
57
57
|
export type OpenApiRouteHeaderParam<T> = T extends {
|
|
58
58
|
get: {
|
|
@@ -63,14 +63,14 @@ export type OpenApiRouteHeaderParam<T> = T extends {
|
|
|
63
63
|
}
|
|
64
64
|
? LowercaseKeys<U>
|
|
65
65
|
: T extends {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
post: {
|
|
67
|
+
parameters: {
|
|
68
|
+
header: infer U extends Record<string | number | symbol, any>
|
|
69
|
+
}
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
: unknown
|
|
72
|
+
? LowercaseKeys<U>
|
|
73
|
+
: unknown
|
|
74
74
|
|
|
75
75
|
export type OpenApiRouteParam<T> = OpenApiRoutePathParam<T> &
|
|
76
76
|
OpenApiRouteQueryParam<T> &
|
|
@@ -79,7 +79,7 @@ export type OpenApiRouteParam<T> = OpenApiRoutePathParam<T> &
|
|
|
79
79
|
export type OpenApiRouteRequestBodyMethod<
|
|
80
80
|
T,
|
|
81
81
|
TMethod extends string = 'post',
|
|
82
|
-
TDefault = unknown
|
|
82
|
+
TDefault = unknown,
|
|
83
83
|
> = T extends {
|
|
84
84
|
[key in TMethod]: {
|
|
85
85
|
requestBody: {
|
|
@@ -94,7 +94,7 @@ export type OpenApiRouteRequestBodyMethod<
|
|
|
94
94
|
|
|
95
95
|
export type OpenApiRouteRequestBody<
|
|
96
96
|
T,
|
|
97
|
-
TMethod extends string = 'post'
|
|
97
|
+
TMethod extends string = 'post',
|
|
98
98
|
> = OpenApiRouteRequestBodyMethod<
|
|
99
99
|
T,
|
|
100
100
|
TMethod,
|
|
@@ -150,7 +150,7 @@ type MimeContent<MimeType extends string> = {
|
|
|
150
150
|
|
|
151
151
|
type MimeContentValue<
|
|
152
152
|
MimeType extends ApiMimeTypes,
|
|
153
|
-
T extends MimeContent<MimeType
|
|
153
|
+
T extends MimeContent<MimeType>,
|
|
154
154
|
> = {
|
|
155
155
|
[K in keyof T['content']]: T['content'][K]
|
|
156
156
|
}[keyof T['content']]
|
|
@@ -194,7 +194,7 @@ type RouteHandlers<SubsetOperationIds extends OperationIds> = {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
export type RestApiController<
|
|
197
|
-
SubsetOperationIds extends OperationIds = OperationIds
|
|
197
|
+
SubsetOperationIds extends OperationIds = OperationIds,
|
|
198
198
|
> = {
|
|
199
199
|
[Key in SubsetOperationIds]: OpenApiHandler<Key>
|
|
200
200
|
}
|
|
@@ -225,7 +225,7 @@ export const openApiRouter = (
|
|
|
225
225
|
route: <
|
|
226
226
|
Method extends keyof paths[Path],
|
|
227
227
|
Path extends keyof paths,
|
|
228
|
-
OperationId extends OperationIds
|
|
228
|
+
OperationId extends OperationIds,
|
|
229
229
|
>(
|
|
230
230
|
method: Method,
|
|
231
231
|
path: Path,
|
|
@@ -245,6 +245,9 @@ export const openApiRouter = (
|
|
|
245
245
|
case 'post':
|
|
246
246
|
router.post(route, handler)
|
|
247
247
|
break
|
|
248
|
+
case 'PUT':
|
|
249
|
+
router.put(route, handler)
|
|
250
|
+
break
|
|
248
251
|
case 'patch':
|
|
249
252
|
router.patch(route, handler)
|
|
250
253
|
break
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import express from 'express'
|
|
2
2
|
import { createRequestLogger } from './rest/middleware/request-logger.js'
|
|
3
|
-
import { Container } from '../container.js'
|
|
4
3
|
import { config } from '../config.js'
|
|
5
4
|
import { createErrorHandler } from './rest/middleware/error-handler.js'
|
|
6
5
|
import { routes } from './rest/routes.js'
|
|
7
6
|
import { createContextMiddleware } from './rest/middleware/context-middleware.js'
|
|
7
|
+
import { ContainerFactory } from '../container.js'
|
|
8
8
|
|
|
9
|
-
export const createServer = (
|
|
10
|
-
const
|
|
9
|
+
export const createServer = async (containerFactory: ContainerFactory) => {
|
|
10
|
+
const container = await containerFactory()
|
|
11
|
+
const { logger } = container
|
|
11
12
|
|
|
12
13
|
const server = express()
|
|
14
|
+
|
|
13
15
|
const errorHandler = createErrorHandler(config.server)
|
|
14
16
|
const requestLogger = createRequestLogger(logger)
|
|
15
|
-
const contextMiddleware = createContextMiddleware(
|
|
17
|
+
const contextMiddleware = createContextMiddleware(container)
|
|
16
18
|
|
|
17
19
|
server.disable('x-powered-by')
|
|
18
20
|
|