kwyjibo
v1.0.30
Published
A set of Typescript Decorators and helpers to write better node.js+Express applications.
Readme
#Kwyjibo A set of TypeScript Decorators and helpers for a better node.js+Express experience.
##TL;DR Watch this video
##Key features
- Requirements
- Express integration
- Controllers and Actions
- Custom mount conditions
- Error Handling
- Tests execution and automation
- Documentation generator
##Quickstart
Install Visual Studio Code
Install
yowith thegenerator-kwyjibopackage, and the required dependencies for every-day development (typescript)npm install --global yo generator-kwyjibo typescript@betaUse the
generator-kwyjiboto scaffold a new web app in the current directory. When asked, give a name to the app and answerYesto every generator optionyo kwyjiboStart Visual Studio Code
code .Press
F1and type>Run Build Taskto build the appPress
F5to start the Kwyjibo app inhttp://localhost:3000You did it! Your Kwyjibo app is up and running!
##Requirements
To use the Kwyjibo framework with a Node.js+Express web app, the minimum requirements are:
- Node.js 6.x
- Express 4.14.0
- TypeScript 2.0.0 (beta), with:
experimentalDecoratorssupport- ECMAScript 6 target
(in your tsconfig.json file, inside compilerOptions set experimentalDecorators to true and target to es6)
##Express integration
The easiest way to use Kwyjibo is using the Yeoman generator, as it is explained in the quickstart. However, if you already have an Express application, or just don't want to use the generator, you can use this steps to integrate Kwyjibo with an existing Express app.
Once you have an Express app up and running (and using TypeScript), go to a terminal and run npm install --save kwyjibo to add it as a dependency to you app. Then, open the app entrypoint (let's say, app.ts) and add the following line at the beginning:
import * as Kwyjibo from "kwyjibo"
And right after creating the Http server, add the following lines (assuming expressApp is an object containing the Express app):
// Init all Kwyjibo controllers and tests (assuming "tests" and "controllers" folders)
// To use custom folders, pass the folder names as extra parameters to initialize
Kwyjibo.initialize(App.express);This will configure the framework loggers, and load all the tests and controllers that are inside the tests and controllers folders
##Controllers and Actions
The main components in a Kwyjibo app are the controllers and their actions. Each controller is a mount point for a set of actions, and each action can handle a request to a specific path and HTTP method.
###Controllers
The controllers must be decorated with the @Controller decorator, specifying the mount point:
@Controller("/myMountPoint")will mount the controller in/myMountPoint.@Controller(AnotherController, "/myMountPoint")will mount the controller in the specified mount point, but usingAnotherControlleras its root (for instance, this could be ended up being mounted as/someMountPoint/myMountPoint.
You can - optionally - add the @DocController decorator to add documentation and the @Middleware decorator to apply middlewares to all the controller actions
###Actions
Each action is a method in the controller with either, a @Get, @Post or @Method decorator to specify its route and HTTP method, or at least one of the @DocAction or @ActionMiddleware decorators, and it will use the default mount point (the method name, using the GET HTTP method)
The @DocAction the same way @DocController does, but for actions, and the @ActionMiddleware to apply middlewares to particular actions.
By default, the action methods receive at least a context: Kwyjibo.Context parameter that allows it to access the request and response objects and can return:
- If the action just returns
200 OK, invokes tocontext.response.renderor manually handles thecontext.response: voidPromise<void>- if the actions returns
200 OKand sends a string as a response (for instance, an HTML): stringPromise<string>- If the action returns a
200 OKand sends ajsonobject as a response: ObjectPromise<Object>
In any of those cases, if an exception is thrown (or the promise is rejected), the exception will be handled by the error handler middlewares configured in Express.
Also, a method can return an HttpError for which the correct status and message will be sent. For example:
async someMethod(context:Context): Promise<Object|HttpError> {
let retObj = await someMethodThatBringsTheObject();
if(retObj == undefined) {
return NotFound("Cannot find the requested object");
}
return retObj;
}###Parameters
To use request parameters from the body, route path, querystring, headers or cookies, you can decorate any action parameter but the first (that must be the context) with the following decorators:
@FromBody("paramName")@FromPath("paramName")@FromQuery("paramName")@FromHeader("paramName")@FromCookie("paramName")
For instance, to create a controller for users operations:
- Create a
controllersfolder in your app root and create ausersController.tsfile inside. - Add the following code:
import * as K from "kwyjibo";
@K.Controller("/users")
@K.DocController("Users Controller.")
@K.Middleware(UsersController.loggingMiddleware)
class UsersController {
static loggingMiddleware(req: Express.Request, res: Express.Response, next: Function) {
console.log("Request to: " + req.path);
}
@K.Get("/")
@K.DocAction(`Users index`)
index(context: K.Context): string {
return "<html><body><ul><li>/list: all users</li><li>/user/:id: specifi user</li></ul></body></html>";
}
@K.Get("/list")
@K.DocAction(`Return all users`)
allUsers(context: Context): Object {
let users = UsersRepository.getAllUsers();
return users; // this action will send a json object
}
@Get("/user/:id")
@DocAction(`Return a specific user`)
oneUser(context: Context, @FromPath("id") id: string): string {
let user = UsersRepository.getUser(id);
return user;
}
}###Migration from standard Express
If you want to use the standard Express route method signature, instead of just receiving the context object (useful to migrate classic Express apps to Kwyjibo), you can use the @ExpressCompatible decorator and create methods like this:
@Get("/somewhere")
@ExpressCompatible()
myExpressCompatibleAction(req: Express.Request, res: Express.Response, next: Function): void {
// do something
}##Custom mount conditions
If you want to mount controllers conditionally, you can use the @MountCondition decorator.
###Dev environment
When the node app is started with the environment variable NODE_ENV = development, every controller that doesn't have it's root endpoint mapped to an action will autogenerate an index with links to every action available at that endpoint.
Also, if you have controllers that should only be exposed in development environment, you can use the @Dev controller decorator (a special case of a custom mount condition) and it will only be mounted if that condition is met.
##Error Handling
Kwijibo will automatically handle errors thrown inside actions and send a 500 Internal Server Error response.
However, you can throw known error types that Kwyjibo can handle:
HttpError: custom HttpError, with message and status codeInternalServerError: 500 error with messageNotFound: 404 error with messageBadRequest: 400 error with messageUnauthorized: 401 error with message
For instance, if you wanted to validate a payload in a web API, you would do something like this:
@Controller("/api")
class Api {
@Get()
doSomething(context: Context, @FromQuery("id") id: string): Object {
if (id == undefined) {
throw new BadRequest("id parameter is required");
}
return {
value: id
};
}
}##Tests execution and automation
The Kwyjibo framework includes the autogeneration of endpoints for integration tests execution, in both interactive and automatic scenarios.
###Test fixture
To add tests to you app, create a sampleTests.ts file inside the tests folder under the app root. The test fixture class must be decorated with @Fixture and each test is a method inside it that has the @Test decorator.
To do the test preparation and cleanup, you can write methods inside the fixture with the @Before and @After decorators.
Each test method can have either void or Promise<void>as its return type.
If the test finishes its execution successfully, will be considered as passed. To make a test fail, it must throw an exeption, or reject the returned promise.
A Test fixture example:
import * as K from "kwyjibo"
@K.Fixture()
export default class Fixture {
@K.Before()
prepare(): void {
// this method will run before the tests
}
@K.Test("A test that passes")
test1(): void {
// this test will pass
}
@K.Test("A test that fails")
test2(): Promise<void> {
return new Promise<void>((resolve, reject) => {
reject(new Error('failed test!'));
});
}
@K.After()
cleanUp(): void {
// this method will run after the tests
}
}###Test runner
Then, you have to add the @TestRunner decorator to a controller. It will scan for all the available test fixtures in the app and generate the endpoints to execute them.
A controller with the autogenerated test endpoints (test runner):
import * as K from "kwyjibo";
@K.TestRunner()
@K.Controller("/test")
export default class Test {
}The interactive test runner will explain how to invoke the same set of tests programatically.
##Documentation generator
Kwyjibo reads all the @DocController and @DocAction decorators and uses that information to automatically generate documentation for your web app or API
There are two functions available to access obtain the generated documentation:
Kwyjibo.getDocs(): returns aControllerDocNode[]representing all the controllers documentation with their actions.Kwyjibo.getDocsAsHTML(): returns a string with the controllers documentation as HTML
For instance, if you wanted to have a documentation endpoint for your web API, and it should only be exposed when running in a dev environment, you should create the following controller:
import * as K from "kwyjibo";
K.Dev()
K.Controller("/docs")
K.DocController("API Documentation")
export default class DocsController {
@K.Get("/")
htmlDocs(context: K.Context): string {
return K.getDocsAsHTML();
}
}
