Skip to content

Commit

Permalink
Adding typescript support (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
evanshortiss authored Oct 28, 2017
1 parent 50f99b3 commit 3c99090
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 38 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
## CHANGELOG
Date format is DD/MM/YYYY

# 0.1.0 (16th April 2017)
## 0.2.0 (28/10/2017)
* Add TypeScript support
* Add new `fields` function for use with express-formidable

## 0.1.0 (16/04/2017)
* Initial release.
45 changes: 24 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# express-joi-validation

![TravisCI](https://travis-ci.org/evanshortiss/express-joi-validation.svg) [![npm version](https://badge.fury.io/js/express-joi-validation.svg)](https://badge.fury.io/js/express-joi-validation) [![Coverage Status](https://coveralls.io/repos/github/evanshortiss/express-joi-validation/badge.svg?branch=master)](https://coveralls.io/github/evanshortiss/express-joi-validation?branch=master)
![TravisCI](https://travis-ci.org/evanshortiss/express-joi-validation.svg)
[![npm version](https://badge.fury.io/js/express-joi-validation.svg)](https://badge.fury.io/js/express-joi-validation)
[![Coverage Status](https://coveralls.io/repos/github/evanshortiss/express-joi-validation/badge.svg?branch=master)](https://coveralls.io/github/evanshortiss/express-joi-validation?branch=master)
[![TypeScript](https://badges.frapsoft.com/typescript/code/typescript.svg?v=101)](https://github.com/ellerbrock/typescript-badges/)

A middleware for validating express inputs using Joi schemas. Fills some of the
voids I found that other Joi middleware miss such as:
Expand Down Expand Up @@ -41,32 +44,22 @@ folder of this repository.
## Usage

```js
const Joi = require('joi');
const validator = require('express-joi-validation')({});

const app = require('express')();
const orders = require('lib/orders');
const Joi = require('joi')
const app = require('express')()
const validator = require('express-joi-validation')({})

const querySchema = Joi.object({
type: Joi.string().required().valid('food', 'drinks', 'entertainment'),
from: Joi.date().iso().required(),
to: Joi.date().iso().min(Joi.ref('from')).required()
});

// Allow unknown fields in the query. This is not allowed by default
const joiOpts = {
allowUnknown: true
};
type: Joi.string().required().valid('food', 'drinks', 'entertainment')
})

app.get('/orders', validator.query(querySchema, {joi: joiOpts}), (req, res, next) => {
console.log(
`Compare the incoming query ${JSON.stringify(req.originalQuery)} vs. the sanatised query ${JSON.stringify(req.query)}`
);
`original query ${JSON.stringify(req.originalQuery)} vs. the sanatised query ${JSON.stringify(req.query)}`
)

// if we're in here then the query was valid!
orders.getForQuery(req.query)
.then((listOfOrders) => res.json(listOfOrders))
.catch(next);
});
res.end(`you placed an order of type ${req.query.type}`)
})
```


Expand Down Expand Up @@ -146,6 +139,11 @@ The following sensible defaults are applied if you pass none:
* allowUnknown: false
* abortEarly: false

#### Fields (with express-formidable)
* convert: true
* allowUnknown: false
* abortEarly: false


## Custom Express Error handler

Expand Down Expand Up @@ -224,6 +222,11 @@ Create a middleware instance that will validate the params for an incoming
request. Can be passed `options` that override the options passed when the
instance was created.

#### instance.fields(schema, [options])
Create a middleware instance that will validate the fields for an incoming
request. This is designed for use with `express-formidable`. Can be passed
`options` that override the options passed when the instance was created.

The `instance.params` middleware is a little different to the others. It _must_
be attached directly to the route it is related to. Here's a sample:

Expand Down
22 changes: 22 additions & 0 deletions example/typescript/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';
exports.__esModule = true;
var port = 3030;
var express = require("express");
var Joi = require("joi");
var validation = require("../../express-joi-validation");
var app = express();
var validator = validation();
var headerSchema = Joi.object({
'host': Joi.string().required(),
'user-agent': Joi.string().required()
});
app.use(validator.headers(headerSchema));
app.get('/ping', function (req, res) { return res.end('pong'); });
app.listen(3030, function (err) {
if (err) {
throw err;
}
console.log("\napp started on " + port + "\n");
console.log("Try accessing http://localhost:" + port + "/users/1001 or http://localhost:" + port + "/users?name=dean to get some data.\n");
console.log("Now try access http://localhost:" + port + "/users?age=50. You should get an error complaining that your querystring is invalid.");
});
29 changes: 29 additions & 0 deletions example/typescript/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict'

const port = 3030

import * as express from 'express'
import * as Joi from 'joi'
import * as validation from '../../express-joi-validation'

const app = express()
const validator = validation()

const headerSchema = Joi.object({
'host': Joi.string().required(),
'user-agent': Joi.string().required()
})

app.use(validator.headers(headerSchema))

app.get('/ping', (req, res) => res.end('pong'))

app.listen(port, (err: any) => {
if (err) {
throw err
}

console.log(`\napp started on ${port}\n`)
console.log(`Try accessing http://localhost:${port}/users/1001 or http://localhost:${port}/users?name=dean to get some data.\n`)
console.log(`Now try access http://localhost:${port}/users?age=50. You should get an error complaining that your querystring is invalid.`)
})
38 changes: 38 additions & 0 deletions express-joi-validation.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as Joi from 'joi';
import * as express from 'express'

declare module 'express' {
interface Request {
originalBody: Array<any>|object|undefined
originalQuery: object
originalHeaders: object
originalParams: object
originalFields: object
}
}

interface ExpressJoiConfig {
joi?: typeof Joi
statusCode?: number
passError?: boolean
}

interface ExpressJoiContainerConfig {
joi?: Joi.ValidationOptions
statusCode?: number
passError?: boolean
}

interface ExpressJoiInstance {
body (schema: Joi.Schema, cfg?: ExpressJoiContainerConfig): express.RequestHandler
query (schema: Joi.Schema, cfg?: ExpressJoiContainerConfig): express.RequestHandler
params (schema: Joi.Schema, cfg?: ExpressJoiContainerConfig): express.RequestHandler
headers (schema: Joi.Schema, cfg?: ExpressJoiContainerConfig): express.RequestHandler
fields (schema: Joi.Schema, cfg?: ExpressJoiContainerConfig): express.RequestHandler
}

declare function validation (cfg? : ExpressJoiConfig): ExpressJoiInstance

declare namespace validation {}

export = validation
11 changes: 11 additions & 0 deletions index.js → express-joi-validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const containers = {
abortEarly: false
}
},
// For use with body-parser
body: {
storageProperty: 'originalBody',
joi: {
Expand All @@ -27,13 +28,23 @@ const containers = {
abortEarly: false
}
},
// URL params e.g "/users/:userId"
params: {
storageProperty: 'originalParams',
joi: {
convert: true,
allowUnknown: false,
abortEarly: false
}
},
// For use with express-formidable or similar POST body parser for forms
fields: {
storageProperty: 'originalFields',
joi: {
convert: true,
allowUnknown: false,
abortEarly: false
}
}
};

Expand Down
50 changes: 38 additions & 12 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ describe('express joi', function () {
function getRequester (middleware) {
const app = require('express')();

// POST/PUT need body parser
app.use(require('body-parser').json());

// Must apply params middleware inline with the route to match param names
app.get('/params-check/:key', middleware, (req, res) => {
expect(req.params).to.exist;
Expand All @@ -26,11 +23,8 @@ describe('express joi', function () {
res.end('ok');
});

// Apply validation middleware supplied
app.use(middleware);

// Configure some dummy routes
app.get('/headers-check', (req, res) => {
app.get('/headers-check', middleware, (req, res) => {
expect(req.headers).to.exist;
expect(req.originalHeaders).to.exist;

Expand All @@ -40,7 +34,7 @@ describe('express joi', function () {
res.end('ok');
});

app.get('/query-check', (req, res) => {
app.get('/query-check', middleware, (req, res) => {
expect(req.query).to.exist;
expect(req.originalQuery).to.exist;

Expand All @@ -50,7 +44,7 @@ describe('express joi', function () {
res.end('ok');
});

app.post('/body-check', (req, res) => {
app.post('/body-check', require('body-parser').json(), middleware, (req, res) => {
expect(req.body).to.exist;
expect(req.originalBody).to.exist;

Expand All @@ -60,6 +54,16 @@ describe('express joi', function () {
res.end('ok');
});

app.post('/fields-check', require('express-formidable')(), middleware, (req, res) => {
expect(req.fields).to.exist;
expect(req.originalFields).to.exist;

expect(req.fields.key).to.be.a('number');
expect(req.originalFields.key).to.be.a('string');

res.end('ok');
});

return supertest(app);
}

Expand All @@ -70,7 +74,7 @@ describe('express joi', function () {
key: Joi.number().integer().min(1).max(10).required()
});

mod = require('./index')();
mod = require('./express-joi-validation.js')();
});

describe('#headers', function () {
Expand Down Expand Up @@ -143,6 +147,28 @@ describe('express joi', function () {
});
});

describe('#fields', function () {
it('should return a 200 since our fields are valid', function (done) {
const mw = mod.fields(schema);

getRequester(mw).post('/fields-check')
.field('key', '1')
.expect(200)
.end(done);
});

it('should return a 400 since our body is invalid', function (done) {
const mw = mod.fields(schema);

getRequester(mw).post('/fields-check')
.expect(400)
.end(function (err, res) {
expect(res.text).to.contain('"key" is required');
done();
});
});
});

describe('#params', function () {
it('should return a 200 since our request param is valid', function (done) {
const mw = mod.params(schema);
Expand All @@ -166,7 +192,7 @@ describe('express joi', function () {

describe('optional configs', function () {
it('should call next on error via config.passError', function (done) {
const mod = require('./index.js')({
const mod = require('./express-joi-validation.js')({
passError: true
});
const mw = mod.query(Joi.object({
Expand Down Expand Up @@ -209,7 +235,7 @@ describe('express joi', function () {
};
resStub.status = sinon.stub().returns(resStub);

const mod = require('./index.js')({
const mod = require('./express-joi-validation.js')({
joi: joiStub,
statusCode: statusCode
});
Expand Down
9 changes: 9 additions & 0 deletions loadtest
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash
x=1

while [ $x -le 10 ]
do
ab -n 1000 -c 100 -q http://127.0.0.1:8080/users?name=dean | grep -i "Requests per second"
x=$(( $x + 1 ))
sleep 5
done
18 changes: 14 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@
"name": "express-joi-validation",
"version": "0.1.0",
"description": "validate express application inputs and parameters using joi",
"main": "index.js",
"main": "express-joi-validation.js",
"scripts": {
"unit": "mocha *.test.js",
"test": "npm run cover && nyc check-coverage --statements 100 --lines 100 --functions 100 --branches 100",
"ts-test": "tsc express-joi-validation.d.ts --target es5 --module commonjs --noEmit",
"test": "npm run ts-test && npm run cover && nyc check-coverage --statements 100 --lines 100 --functions 100 --branches 100",
"cover": "nyc --reporter=lcov --produce-source-map=true npm run unit",
"example": "nodemon example/server.js",
"example-ts": "tsc example/typescript/server.ts && node example/typescript/server.js",
"coveralls": "npm run cover && cat coverage/lcov.info | coveralls"
},
"files": ["index.js"],
"files": [
"express-joi-validation.js",
"express-joi-validation.d.ts"
],
"keywords": [
"joi",
"express",
Expand All @@ -31,6 +36,7 @@
"clear-require": "~2.0.0",
"coveralls": "~2.13.0",
"express": "~4.15.2",
"express-formidable": "~1.0.0",
"joi": "~10.2.2",
"lodash": "~4.17.4",
"mocha": "~3.2.0",
Expand All @@ -39,12 +45,16 @@
"nyc": "~10.2.0",
"proxyquire": "~1.7.11",
"sinon": "~1.17.7",
"supertest": "~3.0.0"
"supertest": "~3.0.0",
"typescript": "~2.5.3"
},
"peerDependencies": {
"joi": "*"
},
"engines": {
"node": ">=4.0.0"
},
"dependencies": {
"@types/express": "~4.0.39"
}
}
9 changes: 9 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": true
}
}

0 comments on commit 3c99090

Please sign in to comment.