Skip to content

Commit

Permalink
feat(controller): adding CRUD base, error-handling, pipe decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
Cervantes007 authored and Lino Alejandro Martell Guevara committed Sep 26, 2020
1 parent 244ab24 commit fb1cfab
Show file tree
Hide file tree
Showing 21 changed files with 1,028 additions and 673 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,6 @@ dist
# TernJS port file
.tern-port
.idea
.vscode
lib
docs
4 changes: 2 additions & 2 deletions __test__/injectable.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getIntance, injectableInstance } from '../src/decorators'
import { getInstance, injectableInstance } from '../src/decorators'

describe('Injectable Test', () => {
test('Injectable from instance', () => {
Expand All @@ -20,7 +20,7 @@ describe('Injectable Test', () => {
expect(b.a.getName()).toBe(a.getName())
}

const aInstance = getIntance(A)
const aInstance = getInstance(A)
expect(aInstance.getName()).toBe(a.getName())
})
})
2 changes: 1 addition & 1 deletion __test__/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { start, close } from '../src'

beforeAll(async () => {
await start(3000, { logger: false }, { pattern: 'api/**/*.@(ctrl|controller).ts' })
await start(3066, { logger: false }, { pattern: 'api/**/*.@(ctrl|controller).ts' })
})

afterAll(async () => {
Expand Down
2 changes: 1 addition & 1 deletion __test__/start.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { start } from '../src'

test('Start app', async () => {
try {
await start(3000)
await start(3066)
} catch (e) {
expect(e.code).toBe('FST_ERR_REOPENED_SERVER')
}
Expand Down
12 changes: 6 additions & 6 deletions api/cats/cats.ctrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,27 @@ export class CatsController {

@Get('/redirect')
@Redirect('/')
redirect() {
redirect(): string {
return 'redirect'
}

@Get('/name/:name')
findByName(@Request() request: any, @Reply() reply: any) {
reply.send(`This action returns cat by name ${request.params.name}`)
findByName(@Request() request: any, @Reply() reply: any): Promise<string> {
return reply.send(`This action returns cat by name ${request.params.name}`)
}

@Get(':id')
findOne(@Param('id') id: string) {
findOne(@Param('id') id: string): string {
return this.catsService.findOne(id)
}

@Put(':id')
update(@Param('id') id: string, @Body() updateCatDto: any) {
update(@Param('id') id: string, @Body() updateCatDto: any): string {
return `This action updates a #${id} cat ${updateCatDto}`
}

@Delete(':id')
remove(@Param('id') id: string) {
remove(@Param('id') id: string): string {
return `This action removes a #${id} cat`
}
}
18 changes: 18 additions & 0 deletions api/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2017",
"module": "commonjs",
"sourceMap": true,
"declaration": true,
"removeComments": false,
"noImplicitAny": false,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["./**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
56 changes: 33 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@
"types": "lib/types/index.d.ts",
"repository": "git@github.com:Cervantes007/moonshard.git",
"description": "Nodejs framework to build high-performance app in no time",
"keywords": ["node", "nodejs", "framework", "typescript", "fast", "blazing", "performance", "decorator"],
"keywords": [
"node",
"nodejs",
"framework",
"typescript",
"fast",
"blazing",
"performance",
"decorator"
],
"author": "Cervantes",
"license": "MIT",
"scripts": {
Expand All @@ -21,7 +30,7 @@
"is:ready": "yarn lint && yarn build && yarn test:ready",
"lint": "eslint --ignore-pattern '/lib/' '*/**/*.ts' --quiet --fix",
"semver": "semantic-release",
"test": "jest --clearCache && jest",
"test": "jest --clearCache && jest -i",
"test:coverage": "jest -i --coverage",
"test:dev": "jest --watch",
"test:ready": "jest --clearCache && jest -i --coverage"
Expand All @@ -46,36 +55,37 @@
},
"dependencies": {
"chalk": "^4.1.0",
"fastify": "^3.0.3",
"fastify": "^3.4.1",
"glob": "^7.1.6",
"m16": "^1.0.0",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {
"@semantic-release/changelog": "^5.0.1",
"@semantic-release/git": "^9.0.0",
"@types/jest": "26.0.7",
"@types/node": "^14.0.23",
"@typescript-eslint/eslint-plugin": "^3.6.1",
"@typescript-eslint/parser": "^3.6.1",
"@vuepress/plugin-back-to-top": "^1.5.2",
"@vuepress/plugin-medium-zoom": "^1.5.2",
"cz-conventional-changelog": "3.2.0",
"eslint": "^7.4.0",
"@types/jest": "26.0.14",
"@types/node": "^14.11.2",
"@typescript-eslint/eslint-plugin": "^4.2.0",
"@typescript-eslint/parser": "^4.2.0",
"@vuepress/plugin-back-to-top": "^1.5.4",
"@vuepress/plugin-medium-zoom": "^1.5.4",
"cz-conventional-changelog": "3.3.0",
"eslint": "^7.9.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.4",
"husky": "^4.2.5",
"jest": "^26.1.0",
"lint-staged": "^10.2.11",
"prettier": "^2.0.5",
"pretty-quick": "^2.0.1",
"husky": "^4.3.0",
"jest": "26.4.2",
"lint-staged": "^10.4.0",
"prettier": "^2.1.2",
"pretty-quick": "^3.0.2",
"rimraf": "^3.0.2",
"semantic-release": "^17.1.1",
"ts-jest": "^26.1.2",
"ts-node-dev": "^1.0.0-pre.52",
"typedoc": "^0.17.8",
"typedoc-plugin-markdown": "^2.3.1",
"typescript": "3.9.7",
"vuepress": "^1.5.2",
"semantic-release": "^17.1.2",
"ts-jest": "26.4.0",
"ts-node-dev": "^1.0.0-pre.63",
"typedoc": "^0.19.2",
"typedoc-plugin-markdown": "^3.0.2",
"typescript": "4.0.3",
"vuepress": "^1.5.4",
"vuepress-plugin-one-click-copy": "^1.0.2"
}
}
5 changes: 5 additions & 0 deletions src/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import chalk from 'chalk'
import { FastifyLoggerInstance } from 'fastify/types/logger'
import http from 'http'
import { InjectOptions, Response as LightMyRequestResponse } from 'light-my-request'
import { defaultErrorHandler } from '../error-handling/default-error-handler'

let app

Expand All @@ -18,9 +19,13 @@ export const createApp = <Server extends http.Server, Logger extends FastifyLogg
},
): FastifyInstance => {
const _app = fastify(options)

_app.setErrorHandler(defaultErrorHandler)

if (!app) {
app = _app
}

loadControllers(pattern, { ...globOptions, ...{ matchBase: true } })
return app
}
Expand Down
39 changes: 39 additions & 0 deletions src/crud/crud.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Model, model, Document } from 'm16'
import { Body, Delete, Get, Param, Patch, Post, Put } from '../decorators'

export abstract class Crud<T extends Document = any, CreateDTO = Partial<T>, UpdateDTO = Partial<T>> {
Model: Model<T>
constructor(public Document: new (data: any) => T) {
this.Model = model(Document)
}

@Get()
list() {
return this.Model.find()
}

@Post()
create(@Body() body: CreateDTO) {
const document = new this.Document(body)
return document.save()
}

@Patch(':id')
update(@Param('id') id: number | string, @Body() body: Partial<T>) {
const document = new this.Document(body)
console.log(id)
console.log(document)
return this.Model.findOneAndUpdate({ _id: id }, { $set: document })
}

@Put(':id')
replace(@Param('id') id: number | string, @Body() body: UpdateDTO) {
const document = new this.Document(body)
return this.Model.findOneAndReplace({ _id: id }, document)
}

@Delete(':id')
remove(@Param('id') id: number | string) {
return this.Model.findOneAndDelete({ _id: id })
}
}
1 change: 1 addition & 0 deletions src/crud/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './crud'
7 changes: 6 additions & 1 deletion src/decorators/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@ export const Controller = (options: ControllerOptions | string = '/'): any => (t
for (const route of _routes) {
const { paramsMetadata, handler, status, ..._route } = route
const _status = status || getStatusByMethod(route.method)
_route.handler = (request, reply) => {
if (route.pipe) {
_route.preHandler = route.pipe
}

_route.handler = async (request, reply) => {
const params = injectParams({ paramsMetadata, request, reply: reply.status(_status) })
return handler.call(instance, ...params)
}

server.route(_route)
}
}, _options)
Expand Down
2 changes: 1 addition & 1 deletion src/decorators/injectable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export function injectableInstance(instance): void {
Reflect.defineMetadata(MOONSHARD_INJECTABLE, instance, instance.constructor)
}

export function getIntance<T>(target: new () => T): T {
export function getInstance<T>(target: new () => T): T {
return Reflect.getMetadata(MOONSHARD_INJECTABLE, target)
}

Expand Down
13 changes: 12 additions & 1 deletion src/decorators/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MOONSHARD_ROUTES } from '../utils/constants'
export const Get = buildMethodDecorator(RequestMethod.GET)
export const Post = buildMethodDecorator(RequestMethod.POST)
export const Put = buildMethodDecorator(RequestMethod.PUT)
export const PATCH = buildMethodDecorator(RequestMethod.PATCH)
export const Patch = buildMethodDecorator(RequestMethod.PATCH)
export const Delete = buildMethodDecorator(RequestMethod.DELETE)
export const Options = buildMethodDecorator(RequestMethod.OPTIONS)
export const Head = buildMethodDecorator(RequestMethod.HEAD)
Expand All @@ -22,6 +22,17 @@ export const HttpCode = (status: number) => (target, key: string | symbol) => {
}
}

export const Pipe = (...funtions) => (target, key: string | symbol) => {
if (funtions) {
const routes = Reflect.getMetadata(MOONSHARD_ROUTES, target.constructor) || {}
if (!routes[key]) {
routes[key] = {}
}
routes[key].pipe = funtions
Reflect.defineMetadata(MOONSHARD_ROUTES, routes, target.constructor)
}
}

export const Header = (name: string, value: string) => (target, key: string | symbol) => {
if (name && value) {
const routes = Reflect.getMetadata(MOONSHARD_ROUTES, target.constructor) || {}
Expand Down
10 changes: 10 additions & 0 deletions src/error-handling/default-error-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { HttpStatus } from '../enums'
import { HttpError } from './errors/http-error'

export function defaultErrorHandler(error, request, reply) {
if (error instanceof HttpError && typeof error.getResponse === 'function') {
const data = error.getResponse({ request, reply })
return reply.code(data.code).send(data)
}
return reply.code(HttpStatus.INTERNAL_SERVER_ERROR).send({ message: 'Internal Error Server' })
}
8 changes: 8 additions & 0 deletions src/error-handling/errors/bad-request-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { HttpStatus } from './../../enums/http-status.enum'
import { HttpError } from './http-error'

export class BadRequestError extends HttpError {
constructor(message: string, error = 'Bad Request', code = HttpStatus.BAD_REQUEST) {
super({ code, error, message })
}
}
45 changes: 45 additions & 0 deletions src/error-handling/errors/http-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { HttpStatus } from './../../enums/http-status.enum'

interface IHttpError {
error?: string
code?: number
message?: string | Record<string, any> | any
}

interface IHttpErrorStatics {
timestamp?: string
path?: string
}

interface IGetResponseParams {
reply?: any
request?: any
}

export abstract class HttpError extends Error {
public readonly code
public readonly error

constructor(
{ error, code, message }: IHttpError = {
code: HttpStatus.INTERNAL_SERVER_ERROR,
error: 'Internal Server Error',
message: '',
},
) {
super()
this.code = code
this.error = error
this.message = message
}

getResponse({ request }: IGetResponseParams = {}): IHttpError & IHttpErrorStatics {
return {
code: this.code,
error: this.error,
['message']: this.message || undefined,
timestamp: new Date().toISOString(),
['path']: request ? request.url : undefined,
}
}
}
8 changes: 8 additions & 0 deletions src/error-handling/errors/internal-server.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { HttpStatus } from './../../enums/http-status.enum'
import { HttpError } from './http-error'

export class InternalServerError extends HttpError {
constructor(message: string, error = 'Internal Server Error', code = HttpStatus.INTERNAL_SERVER_ERROR) {
super({ code, error, message })
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './app'
export * from './decorators'
export * from './crud'
1 change: 0 additions & 1 deletion vuepress/guides/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ In order to create a basic controller, we use classes and decorators.
Decorators associate classes with required metadata and enable Moonshard to create a routing map (tie requests to the corresponding controllers).



To define a `Controller` you need to steps.
1. create a file with a name ending on `.controller.ts` or `.ctrl.ts`. e.g. `app.controller.ts` or `app.ctrl.ts`,
2. Write a class inside the create file and decorate it with `@Controller()`.
Expand Down
2 changes: 1 addition & 1 deletion vuepress/guides/get-started.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Get Started

Moonshard provide an opinionated API over [fastify](https://fastify.io) framework,
it was created to allow fast development phase and incredible performance.
it was created to allow fast development phase and blazing production performance.

To start a server just execute the `start` function:

Expand Down

0 comments on commit fb1cfab

Please sign in to comment.