https://github.com/alextanhongpin/typescript-node-clean-architecture
Figuring out clean architecture design with TypeScript
https://github.com/alextanhongpin/typescript-node-clean-architecture
architecture koa nodejs typescript
Last synced: 2 months ago
JSON representation
Figuring out clean architecture design with TypeScript
- Host: GitHub
- URL: https://github.com/alextanhongpin/typescript-node-clean-architecture
- Owner: alextanhongpin
- Created: 2018-12-11T15:42:42.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2019-05-18T00:32:10.000Z (about 7 years ago)
- Last Synced: 2025-03-24T16:15:11.410Z (about 1 year ago)
- Topics: architecture, koa, nodejs, typescript
- Language: TypeScript
- Size: 163 KB
- Stars: 1
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# clean-typescript
A better architecture for microservice.
## Init
```bash
$ tsc --init
npm init # and follow the resulting prompts to set up the project
npm i koa koa-router koa-body
npm i --save-dev typescript ts-node nodemon
npm i --save-dev @types/koa @types/koa-router
```
## Structure
In order to avoid `shotgun surgery`, whereby making changes will make us change implementations in different places of the codebase, it is preferable to group similar behaviours close to one another.
```js
function decorator(obj, ...decorators) {
return decorators.reduce((o, fn) => fn(o), obj)
}
function endpointBuilder({
requestParser,
statusCode,
service,
responseParser,
middlewares
}) {
return function endpoint(req, res) {
try {
const ctx = {
...res.locals
}
const request = requestParser({
body: req.body,
params: req.params,
query: req.query
})
const response = await service(ctx, decorator(request, ...middlewares))
res.status(statusCode()).json(responseParser(response))
} catch (error) {
return res.status(400).json({
error: error.message
})
}
}
}
function requestParser({
body
}) {
return {
a: body.a,
b: body.b
}
}
function responseParser(response) {
return {
sum: response
}
}
// What if service requires a dependency (database, logger etc)?
function sumServiceBuilder(repo) {
return async function sumService({
a,
b
}) {
const response = a + b
await repo(a, b, response)
return response
}
}
function repositoryBuilder(db) {
return async function repository(a, b, sum) {
const [result] = await db.query('INSERT INTO sum (a, b, sum) VALUES (?, ?, ?)', [a, b, sum])
return result.insertId
}
}
function validator(request) {
const {
a,
b
} = request
if (!isDefined(a)) {
throw new Error('a is required')
}
if (!isDefined(b)) {
throw new Error('b is required')
}
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('invalid number')
}
return request
}
function isDefined(a) {
return a !== null && a !== undefined
}
const getSumEndpointBuilder = function builder(db) {
return endpointBuilder({
requestParser,
statusCode: () => 200,
service: sumServiceBuilder(repositoryBuilder(db)),
responseParser,
middlewares: [validator]
})
}
module.exports = getSumEndpointBuilder
```
## Synchronous Decorator
```js
function validate(request) {
const {
a,
b
} = request
if (!a || !b) {
throw new Error('missing fields')
}
console.log('validated')
return request
}
function multiply(request) {
const {
a,
b
} = request
console.log('multiplied')
return {
a: a * 10,
b: b * 10
}
}
function decorator(value, ...fns) {
return fns.reduce((acc, fn) => fn(acc), value)
}
const request = {
a: 1,
b: 2
}
console.log('response is:', decorator(request, validate, multiply))
```
## Simplify Type
```ts
// We don't need to declare an interface here, use a type with declaration merging instead.
export interface IConfig {
host: string;
port: number;
secret: string;
credential: string;
}
export const Config = () => {
return {
host: process.env.HOST || 'localhost',
port: Number(process.env.PORT || 4040),
secret: process.env.SECRET || 'secret',
credential: process.env.CREDENTIAL || 'hashed credentials',
};
};
// Now Config is both a type and function.
export type Config = ReturnType;
```