From dbb26829fdfcf4dfe6fd09424a29412788b2e821 Mon Sep 17 00:00:00 2001 From: Evan Shortiss Date: Wed, 26 Jun 2019 18:28:22 -0700 Subject: [PATCH 01/10] BREAKING CHANGE: version 2.0 improved ts support and module interface change --- .gitignore | 3 +- CHANGELOG.md | 5 +++ README.md | 81 ++++++++++++++++++++++++++++-------- example/typescript/route.ts | 21 ++++++++++ example/typescript/server.js | 34 --------------- example/typescript/server.ts | 19 ++++++--- express-joi-validation.d.ts | 44 ++++++++++++-------- express-joi-validation.js | 6 ++- package.json | 21 ++++++++-- 9 files changed, 153 insertions(+), 81 deletions(-) create mode 100644 example/typescript/route.ts delete mode 100644 example/typescript/server.js diff --git a/.gitignore b/.gitignore index d224b2f..37b553d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules *.log coverage .nyc_output -package-lock.json \ No newline at end of file +package-lock.json +example/typescript/*.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 698f5f7..1fefece 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ ## CHANGELOG Date format is DD/MM/YYYY +## 2.0.0 (27/06/2019) +* Improved TypeScript support with better typings +* Changed export from a factory function to a module exposing `createValidator()` +* Improved TypeScript examples and README + ## 1.0.0 (13/06/2019) * Migrated from `joi` to `@hapi/joi`. * Dropped Node.js 6 & 7 support (@hapi/joi forces this) diff --git a/README.md b/README.md index ef6caad..49bef7b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ A middleware for validating express inputs using Joi schemas. Fills some of the voids I found that other Joi middleware miss such as: +* TypeScript support. * Allow the developers to easily specify the order in which request inputs are validated. * Replaces the incoming `req.body` and others with converted Joi values. The @@ -26,13 +27,16 @@ using a fixed version. ## Install -You need to install `joi` along with this module for it to work since it relies -on it as a peer dependency. Currently this module has only been tested with joi -version 10.0 and higher. +You need to install `@hapi/joi` along with this module for it to work since it relies +on it as a peer dependency. For TypeScript you should also install `@types/hapi__joi`. + +For example example ``` -# we install our middleware AND joi since it's required by our middleware -npm i express-joi-validation joi --save +npm i express-joi-validation @hapi/joi --save + +# If using TypeScript (also useful for JavaScript developers) +npm i @types/hapi__joi --save-dev ``` @@ -41,13 +45,12 @@ npm i express-joi-validation joi --save An example application can be found in the [example/](https://github.com/evanshortiss/express-joi-validation/tree/master/example) folder of this repository. - -## Usage +## Usage (JavaScript) ```js const Joi = require('joi') const app = require('express')() -const validator = require('express-joi-validation')({ +const validator = require('express-joi-validation').createValidator({ // You can pass a specific Joi instance using this option. By default the // module will load the @hapi/joi version you have in your package.json // so 99% of the time you won't need this option @@ -55,16 +58,60 @@ const validator = require('express-joi-validation')({ }) const querySchema = Joi.object({ - type: Joi.string().required().valid('food', 'drinks', 'entertainment') + name: Joi.string().required() }) -app.get('/orders', validator.query(querySchema, {joi: joiOpts}), (req, res, next) => { - console.log( - `original query ${JSON.stringify(req.originalQuery)} vs. the sanatised query ${JSON.stringify(req.query)}` - ) - - // if we're in here then the query was valid! - res.end(`you placed an order of type ${req.query.type}`) +app.get('/orders', validator.query(querySchema), (req, res) => { + // If we're in here then the query was valid! + res.end(`Hello ${req.query.name}!`) +}) +``` + +## Usage (TypeScript) + +For TypeScript you a helper `ValidatedRequest` is provided. This extends the +`express.Request` type and allows you to pass a schema using generics to +ensure type safety in your handler function. + +One downside to this is that there's some duplication. You can minimise this +duplication by using [joi-extract-type](https://github.com/TCMiranda/joi-extract-type/). + +```ts +import * as Joi from '@hapi/joi' +import * as express from 'express' +import { + // Use this as a replacement for express.Request + ValidatedRequest, + // Extend from this to define a valid schema type/interface + ValidatedRequestSchema, + // Creates a validator that generates middlewares + createValidator +} from 'express-joi-validation' + +// Extends Joi to generate our ValidatedRequestSchema members +// This is optional, but without it you need to manually generate +// a type or interface +import 'joi-extract-type' + +const app = express() +const validator = createValidator() + +const querySchema = Joi.object({ + name: Joi.string().required() +}) + +interface HelloRequestSchema extends ValidatedRequestSchema { + query: Joi.extractType + + // Without Joi.extractType we'd need to use the following: + // query: { + // name: string + // } +} + +app.get('/hello', validator.query(querySchema), (req: ValidatedRequest, res) => { + // Woohoo, type safety for req.query! + res.end(`Hello ${req.query.name}!`) }) ``` @@ -157,7 +204,7 @@ If you don't like the default error format returned by this module you can override it like so: ```js -const validator = require('express-joi-validation')({ +const validator = require('express-joi-validation').createValidator({ passError: true // NOTE: this tells the module to pass the error along for you }); diff --git a/example/typescript/route.ts b/example/typescript/route.ts new file mode 100644 index 0000000..aafcfb2 --- /dev/null +++ b/example/typescript/route.ts @@ -0,0 +1,21 @@ + +import * as Joi from '@hapi/joi' +import { ValidatedRequest, ValidatedRequestSchema, createValidator } from '../../express-joi-validation' +import { Router } from 'express'; +import 'joi-extract-type' + +const route = Router() +const validator = createValidator() +const querySchema = Joi.object({ + name: Joi.string().required() +}) + +interface HelloRequestSchema extends ValidatedRequestSchema { + query: Joi.extractType +} + +route.get('/hello', validator.query(querySchema), (req: ValidatedRequest, res) => { + res.end(`Hello ${req.query.name}`) +}) + +export = route diff --git a/example/typescript/server.js b/example/typescript/server.js deleted file mode 100644 index e49efa6..0000000 --- a/example/typescript/server.js +++ /dev/null @@ -1,34 +0,0 @@ -'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.' - ) -}) diff --git a/example/typescript/server.ts b/example/typescript/server.ts index ead8f2f..97b04bf 100644 --- a/example/typescript/server.ts +++ b/example/typescript/server.ts @@ -3,20 +3,27 @@ const port = 3030 import * as express from 'express' -import * as Joi from 'joi' -import * as validation from '../../express-joi-validation' +import * as Joi from '@hapi/joi' +import * as HelloWorld from './route' +import { createValidator } from '../../express-joi-validation'; const app = express() -const validator = validation() +const validator = createValidator() const headerSchema = Joi.object({ host: Joi.string().required(), 'user-agent': Joi.string().required() }) +// Validate headers for all incoming requests app.use(validator.headers(headerSchema)) -app.get('/ping', (req, res) => res.end('pong')) +// No extra validations performed on this simple ping endpoint +app.get('/ping', (req, res) => { + res.end('pong') +}) + +app.use('/hello', HelloWorld) app.listen(port, (err: any) => { if (err) { @@ -25,9 +32,9 @@ app.listen(port, (err: any) => { 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` + `Try accessing http://localhost:${port}/ping or http://localhost:${port}/hello?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.` + `Now try access hhttp://localhost:${port}/hello. You should get an error complaining that your querystring is invalid.` ) }) diff --git a/express-joi-validation.d.ts b/express-joi-validation.d.ts index 607edd5..5cc0fdc 100644 --- a/express-joi-validation.d.ts +++ b/express-joi-validation.d.ts @@ -1,29 +1,43 @@ -import * as Joi from 'joi'; +import * as Joi from '@hapi/joi'; import * as express from 'express' +import { IncomingHttpHeaders } from 'http'; -declare module 'express' { - interface Request { - originalBody: Array|object|undefined - originalQuery: object - originalHeaders: object - originalParams: object - originalFields: object - } +export function createValidator (cfg? : ExpressJoiConfig): ExpressJoiInstance + +export interface ValidatedRequest extends express.Request { + body: T['body'] + query: T['query'] + headers: T['headers'] + params: T['params'] + fields: T['fields'] + originalBody: any + originalQuery: any + originalHeaders: IncomingHttpHeaders + originalParams: any + originalFields: any +} + +export interface ValidatedRequestSchema { + body?: any + query?: any + headers?: any + params?: any + fields?: any } -interface ExpressJoiConfig { +export interface ExpressJoiConfig { joi?: typeof Joi statusCode?: number passError?: boolean } -interface ExpressJoiContainerConfig { +export interface ExpressJoiContainerConfig { joi?: Joi.ValidationOptions statusCode?: number passError?: boolean } -interface ExpressJoiInstance { +export 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 @@ -31,9 +45,3 @@ interface ExpressJoiInstance { fields (schema: Joi.Schema, cfg?: ExpressJoiContainerConfig): express.RequestHandler response (schema: Joi.Schema, cfg?: ExpressJoiContainerConfig): express.RequestHandler } - -declare function validation (cfg? : ExpressJoiConfig): ExpressJoiInstance - -declare namespace validation {} - -export = validation diff --git a/express-joi-validation.js b/express-joi-validation.js index fab3de6..729e2a4 100644 --- a/express-joi-validation.js +++ b/express-joi-validation.js @@ -59,7 +59,11 @@ function buildErrorString(err, container) { return ret } -module.exports = function generateJoiMiddlewareInstance(cfg) { +module.exports = function () { + throw new Error('express-joi-validation: exported member is no longer a factory function. use exported createValidator function instead') +} + +module.exports.createValidator = function generateJoiMiddlewareInstance(cfg) { cfg = cfg || {} // default to an empty config const Joi = cfg.joi || require('@hapi/joi') diff --git a/package.json b/package.json index da6e411..3db0afe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "express-joi-validation", - "version": "1.0.0", + "version": "2.0.0", "description": "validate express application inputs and parameters using joi", "main": "express-joi-validation.js", "scripts": { @@ -36,7 +36,7 @@ "devDependencies": { "@hapi/joi": "~15.0.3", "@types/express": "~4.0.39", - "@types/joi": "~13.6.0", + "@types/hapi__joi": "~15.0.2", "@types/node": "^6.0.117", "body-parser": "~1.18.3", "chai": "~3.5.0", @@ -45,6 +45,7 @@ "express": "~4.16.3", "express-formidable": "~1.0.0", "husky": "~1.0.1", + "joi-extract-type": "~15.0.0", "lodash": "~4.17.4", "mocha": "~5.2.0", "mocha-lcov-reporter": "~1.3.0", @@ -54,12 +55,24 @@ "proxyquire": "~1.7.11", "sinon": "~1.17.7", "supertest": "~3.0.0", - "typescript": "~2.5.3" + "typescript": "~3.5.2" }, "peerDependencies": { "@hapi/joi": "*" }, "engines": { "node": ">=8.0.0" - } + }, + "directories": { + "example": "example" + }, + "dependencies": {}, + "repository": { + "type": "git", + "url": "git+https://github.com/evanshortiss/express-joi-validation.git" + }, + "bugs": { + "url": "https://github.com/evanshortiss/express-joi-validation/issues" + }, + "homepage": "https://github.com/evanshortiss/express-joi-validation#readme" } From 9f1a160cd86f6d9ae4b25c2d087557e7ecc38056 Mon Sep 17 00:00:00 2001 From: Evan Shortiss Date: Wed, 26 Jun 2019 18:37:21 -0700 Subject: [PATCH 02/10] fix: tests and formatting --- example/typescript/route.ts | 19 +++++++++++++------ example/typescript/server.ts | 2 +- express-joi-validation.js | 6 ++++-- index.test.js | 6 +++--- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/example/typescript/route.ts b/example/typescript/route.ts index aafcfb2..36d0fc9 100644 --- a/example/typescript/route.ts +++ b/example/typescript/route.ts @@ -1,7 +1,10 @@ - import * as Joi from '@hapi/joi' -import { ValidatedRequest, ValidatedRequestSchema, createValidator } from '../../express-joi-validation' -import { Router } from 'express'; +import { + ValidatedRequest, + ValidatedRequestSchema, + createValidator +} from '../../express-joi-validation' +import { Router } from 'express' import 'joi-extract-type' const route = Router() @@ -14,8 +17,12 @@ interface HelloRequestSchema extends ValidatedRequestSchema { query: Joi.extractType } -route.get('/hello', validator.query(querySchema), (req: ValidatedRequest, res) => { - res.end(`Hello ${req.query.name}`) -}) +route.get( + '/hello', + validator.query(querySchema), + (req: ValidatedRequest, res) => { + res.end(`Hello ${req.query.name}`) + } +) export = route diff --git a/example/typescript/server.ts b/example/typescript/server.ts index 97b04bf..9f2ab25 100644 --- a/example/typescript/server.ts +++ b/example/typescript/server.ts @@ -5,7 +5,7 @@ const port = 3030 import * as express from 'express' import * as Joi from '@hapi/joi' import * as HelloWorld from './route' -import { createValidator } from '../../express-joi-validation'; +import { createValidator } from '../../express-joi-validation' const app = express() const validator = createValidator() diff --git a/express-joi-validation.js b/express-joi-validation.js index 729e2a4..d6a1b54 100644 --- a/express-joi-validation.js +++ b/express-joi-validation.js @@ -59,8 +59,10 @@ function buildErrorString(err, container) { return ret } -module.exports = function () { - throw new Error('express-joi-validation: exported member is no longer a factory function. use exported createValidator function instead') +module.exports = function() { + throw new Error( + 'express-joi-validation: exported member is no longer a factory function. use exported createValidator function instead' + ) } module.exports.createValidator = function generateJoiMiddlewareInstance(cfg) { diff --git a/index.test.js b/index.test.js index e784e83..12e4c68 100644 --- a/index.test.js +++ b/index.test.js @@ -91,7 +91,7 @@ describe('express joi', function() { .required() }) - mod = require('./express-joi-validation.js')() + mod = require('./express-joi-validation.js').createValidator() }) describe('#headers', function() { @@ -251,7 +251,7 @@ describe('express joi', function() { describe('optional configs', function() { it('should call next on error via config.passError', function(done) { - const mod = require('./express-joi-validation.js')({ + const mod = require('./express-joi-validation.js').createValidator({ passError: true }) const mw = mod.query( @@ -300,7 +300,7 @@ describe('express joi', function() { } resStub.status = sinon.stub().returns(resStub) - const mod = require('./express-joi-validation.js')({ + const mod = require('./express-joi-validation.js').createValidator({ joi: joiStub, statusCode: statusCode }) From b2720fdd68452b1e8fef1df3f7aac63bb81d0333 Mon Sep 17 00:00:00 2001 From: Evan Shortiss Date: Thu, 27 Jun 2019 14:21:16 -0700 Subject: [PATCH 03/10] feat: improve ts example --- example/typescript/route.ts | 5 +++-- example/typescript/server.ts | 13 ++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/example/typescript/route.ts b/example/typescript/route.ts index 36d0fc9..7d3ec34 100644 --- a/example/typescript/route.ts +++ b/example/typescript/route.ts @@ -2,7 +2,8 @@ import * as Joi from '@hapi/joi' import { ValidatedRequest, ValidatedRequestSchema, - createValidator + createValidator, + ContainerTypes } from '../../express-joi-validation' import { Router } from 'express' import 'joi-extract-type' @@ -14,7 +15,7 @@ const querySchema = Joi.object({ }) interface HelloRequestSchema extends ValidatedRequestSchema { - query: Joi.extractType + [ContainerTypes.Query]: Joi.extractType } route.get( diff --git a/example/typescript/server.ts b/example/typescript/server.ts index 9f2ab25..8d917ea 100644 --- a/example/typescript/server.ts +++ b/example/typescript/server.ts @@ -5,7 +5,7 @@ const port = 3030 import * as express from 'express' import * as Joi from '@hapi/joi' import * as HelloWorld from './route' -import { createValidator } from '../../express-joi-validation' +import { createValidator, ExpressJoiError } from '../../express-joi-validation' const app = express() const validator = createValidator() @@ -25,6 +25,17 @@ app.get('/ping', (req, res) => { app.use('/hello', HelloWorld) +// Custom error handler +app.use((err: any|ExpressJoiError, req: express.Request, res: express.Response, next: express.NextFunction) => { + if (err && err.error && err.error.isJoi) { + const e: ExpressJoiError = err + // e.g "you submitted a bad query" + res.status(400).end(`You submitted a bad ${e.type} paramater.`) + } else { + res.status(500).end('internal server error') + } +}) + app.listen(port, (err: any) => { if (err) { throw err From 74e5701b936bad0cf4c5803048f5b7b62ebeb7cc Mon Sep 17 00:00:00 2001 From: Evan Shortiss Date: Thu, 27 Jun 2019 14:23:01 -0700 Subject: [PATCH 04/10] chore: formatting --- README.md | 191 +++++++++++++++++++++-------------- example/typescript/server.ts | 23 +++-- express-joi-validation.d.ts | 64 +++++++++--- package.json | 2 +- 4 files changed, 182 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index 49bef7b..a5ce3f2 100644 --- a/README.md +++ b/README.md @@ -7,43 +7,39 @@ [![npm downloads](https://img.shields.io/npm/dm/express-joi-validation.svg?style=flat)](https://www.npmjs.com/package/express-joi-validation) [![Known Vulnerabilities](https://snyk.io//test/github/evanshortiss/express-joi-validation/badge.svg?targetFile=package.json)](https://snyk.io//test/github/evanshortiss/express-joi-validation?targetFile=package.json) -A middleware for validating express inputs using Joi schemas. Fills some of the -voids I found that other Joi middleware miss such as: +A middleware for validating express inputs using Joi schemas. Features include: * TypeScript support. -* Allow the developers to easily specify the order in which request inputs are -validated. -* Replaces the incoming `req.body` and others with converted Joi values. The -same applies for headers, query, and params, but... -* Retains the original `req.body` inside a new property named `req.originalBody` +* Specify the order in which request inputs are validated. +* Replaces the incoming `req.body`, `req.query`, etc and with the validated result +* Retains the original `req.body` inside a new property named `req.originalBody`. . The same applies for headers, query, and params using the `original` prefix, -e.g `req.originalQuery` will contain the `req.query` as it looked *before* -validation. -* Passes sensible default options to Joi for headers, params, query, and body. -These are detailed below. +e.g `req.originalQuery` +* Chooses sensible default Joi options for headers, params, query, and body. * Uses `peerDependencies` to get a Joi instance of your choosing instead of using a fixed version. ## Install -You need to install `@hapi/joi` along with this module for it to work since it relies -on it as a peer dependency. For TypeScript you should also install `@types/hapi__joi`. - -For example example +You need to install `@hapi/joi` with this module since it relies on it in +`peerDependencies`. ``` npm i express-joi-validation @hapi/joi --save +``` -# If using TypeScript (also useful for JavaScript developers) +For TypeScript developers you also need to install Joi types. JavaScript +developers can benefit from this too: + +``` npm i @types/hapi__joi --save-dev ``` -## Example Code - -An example application can be found in the [example/](https://github.com/evanshortiss/express-joi-validation/tree/master/example) -folder of this repository. +## Example +A JavaScript and TypeScript example can be found in the +[example/](/tree/master/example) folder of this repository. ## Usage (JavaScript) @@ -53,8 +49,7 @@ const app = require('express')() const validator = require('express-joi-validation').createValidator({ // You can pass a specific Joi instance using this option. By default the // module will load the @hapi/joi version you have in your package.json - // so 99% of the time you won't need this option - // joi: require('joi') + // joi: require('@hapi/joi') }) const querySchema = Joi.object({ @@ -69,7 +64,7 @@ app.get('/orders', validator.query(querySchema), (req, res) => { ## Usage (TypeScript) -For TypeScript you a helper `ValidatedRequest` is provided. This extends the +For TypeScript a helper `ValidatedRequest` type is provided. This extends the `express.Request` type and allows you to pass a schema using generics to ensure type safety in your handler function. @@ -88,9 +83,8 @@ import { createValidator } from 'express-joi-validation' -// Extends Joi to generate our ValidatedRequestSchema members // This is optional, but without it you need to manually generate -// a type or interface +// a type or interface for ValidatedRequestSchema members import 'joi-extract-type' const app = express() @@ -101,36 +95,43 @@ const querySchema = Joi.object({ }) interface HelloRequestSchema extends ValidatedRequestSchema { - query: Joi.extractType + [ContainerTypes.Query]: Joi.extractType - // Without Joi.extractType we'd need to use the following: + // Without Joi.extractType you would do this: // query: { // name: string // } } -app.get('/hello', validator.query(querySchema), (req: ValidatedRequest, res) => { - // Woohoo, type safety for req.query! - res.end(`Hello ${req.query.name}!`) -}) +app.get( + '/hello', + validator.query(querySchema), + (req: ValidatedRequest, res) => { + // Woohoo, type safety and intellisense for req.query! + res.end(`Hello ${req.query.name}!`) + } +) ``` ## Behaviours ### Joi Versioning -This module uses `peerDependencies` for the Joi version being used. This means -whatever `@hapi/joi` version is in the `dependencies` of your `package.json` will be -used by this module. +You can explicitly pass a versiong of Joi using the `joi` option supported by +the `createValidator` function. + +Otherwise, this module uses `peerDependencies` for the Joi version being used. +This means whatever `@hapi/joi` version is in the `dependencies` of your +`package.json` will be used by this module. + ### Validation Ordering -If you'd like to validate different request inputs in differing orders it's -simple, just define the the middleware in the order desired. +Validation can be performed in a specific order using standard express +middleware behaviour. Pass the middleware in the desired order. -Here's an example where we do headers, body, and finally the query: +Here's an example where the order is headers, body, query: ```js -// verify headers, then body, then query route.get( '/tickets', validator.headers(headerSchema), @@ -144,8 +145,12 @@ route.get( When validation fails, this module will default to returning a HTTP 400 with the Joi validation error as a `text/plain` response type. -A `passError` option is supported to override this behaviour, and force the -middleware to pass the error to the express error handler you've defined. +A `passError` option is supported to override this behaviour. This option +forces the middleware to pass the error to the express error handler using the +standard `next` function behaviour. + +See the [Custom Express Error Handler](#custom-express-error-handler) section +for an example. ### Joi Options It is possible to pass specific Joi options to each validator like so: @@ -169,7 +174,7 @@ route.get( ); ``` -The following sensible defaults are applied if you pass none: +The following sensible defaults for Joi are applied if none are passed: #### Query * convert: true @@ -198,14 +203,13 @@ The following sensible defaults are applied if you pass none: * abortEarly: false -## Custom Express Error handler - -If you don't like the default error format returned by this module you can -override it like so: +## Custom Express Error Handler ```js const validator = require('express-joi-validation').createValidator({ - passError: true // NOTE: this tells the module to pass the error along for you + // This options forces validation to pass any errors the express + // error handler instead of generating a 400 error + passError: true }); const app = require('express')(); @@ -221,7 +225,7 @@ app.get('/orders', validator.query(require('./query-schema')), (req, res, next) // After your routes add a standard express error handler. This will be passed the Joi // error, plus an extra "type" field so we can tell what type of validation failed app.use((err, req, res, next) => { - if (err.error.isJoi) { + if (err && err.error && err.error.isJoi) { // we had a joi error, let's return a custom 400 json response res.status(400).json({ type: err.type, // will be "query" here, but could be "headers", "body", or "params" @@ -234,56 +238,89 @@ app.use((err, req, res, next) => { }); ``` +In TypeScript environments `err.type` can be verified against the exported +`ContainerTypes`: + +```ts +import { ContainerTypes } from 'express-joi-validation' + +app.use((err: any|ExpressJoiError, req: express.Request, res: express.Response, next: express.NextFunction) => { + // ContainerTypes is an enum exported by this module. It contains strings + // such as "body", "headers", "query"... + if (err && err.type in ContainerTypes) { + const e: ExpressJoiError = err + // e.g "you submitted a bad query paramater" + res.status(400).end(`You submitted a bad ${e.type} paramater`) + } else { + res.status(500).end('internal server error') + } +}) +``` ## API -### module(config) +### Structure + +* module (express-joi-validation) + * createValidator(config) + * query(options) + * body(options) + * params(options) + * headers(options) + * fields(options) + -A factory function an instance of the module for use. Can pass the following -options: +### createValidator(config) +Creates a validator. Supports the following options: -* passError - Set this to true if you'd like validation errors to get passed -to the express error handler so you can handle them manually vs. the default behaviour that returns a 400. -* statusCode - The status code to use when validation fails and _passError_ -is false. Default is 400. +* passError (default: `false`) - Passes validation errors to the express error +hander using `next(err)` when `true` +* statusCode (default: `400`) - The status code used when validation fails and +`passError` is `false`. -### Instance Functions +#### validator.query(schema, [options]) +Creates a middleware instance that will validate the `req.query` for an +incoming request. Can be passed `options` that override the config passed +when the validator was created. -Each instance function can be passed an options Object with the following: +Supported options are: * joi - Custom options to pass to `Joi.validate`. * passError - Same as above. * statusCode - Same as above. -#### instance.query(schema, [options]) -Create a middleware instance that will validate the query for an incoming +#### validator.body(schema, [options]) +Creates a middleware instance that will validate the `req.body` for an incoming request. Can be passed `options` that override the options passed when the -instance was created. +validator was created. -#### instance.body(schema, [options]) -Create a middleware instance that will validate the body for an incoming -request. Can be passed `options` that override the options passed when the -instance was created. +Supported options are the same as `validator.query`. -#### instance.headers(schema, [options]) -Create a middleware instance that will validate the headers for an incoming -request. Can be passed `options` that override the options passed when the -instance was created. +#### validator.headers(schema, [options]) +Creates a middleware instance that will validate the `req.headers` for an +incoming request. Can be passed `options` that override the options passed +when the validator was created. -#### instance.params(schema, [options]) -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. +Supported options are the same as `validator.query`. + +#### validator.params(schema, [options]) +Creates a middleware instance that will validate the `req.params` for an +incoming request. Can be passed `options` that override the options passed +when the validator was created. -#### instance.response(schema, [options]) -Create a middleware instance that will validate the outgoing response. +Supported options are the same as `validator.query`. + +#### validator.response(schema, [options]) +Creates a middleware instance that will validate the outgoing response. 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 +Supported options are the same as `validator.query`. + +#### validator.fields(schema, [options]) +Creates 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. +`options` that override the options passed when the validator 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: @@ -304,3 +341,5 @@ app.get('/orders/:id', validator.params(schema), (req, res, next) => { // This WILL have a validated "id" }) ``` + +Supported options are the same as `validator.query`. diff --git a/example/typescript/server.ts b/example/typescript/server.ts index 8d917ea..a527a18 100644 --- a/example/typescript/server.ts +++ b/example/typescript/server.ts @@ -26,15 +26,22 @@ app.get('/ping', (req, res) => { app.use('/hello', HelloWorld) // Custom error handler -app.use((err: any|ExpressJoiError, req: express.Request, res: express.Response, next: express.NextFunction) => { - if (err && err.error && err.error.isJoi) { - const e: ExpressJoiError = err - // e.g "you submitted a bad query" - res.status(400).end(`You submitted a bad ${e.type} paramater.`) - } else { - res.status(500).end('internal server error') +app.use( + ( + err: any | ExpressJoiError, + req: express.Request, + res: express.Response, + next: express.NextFunction + ) => { + if (err && err.error && err.error.isJoi) { + const e: ExpressJoiError = err + // e.g "you submitted a bad query" + res.status(400).end(`You submitted a bad ${e.type} paramater.`) + } else { + res.status(500).end('internal server error') + } } -}) +) app.listen(port, (err: any) => { if (err) { diff --git a/express-joi-validation.d.ts b/express-joi-validation.d.ts index 5cc0fdc..52084ac 100644 --- a/express-joi-validation.d.ts +++ b/express-joi-validation.d.ts @@ -2,14 +2,50 @@ import * as Joi from '@hapi/joi'; import * as express from 'express' import { IncomingHttpHeaders } from 'http'; +/** + * Creates an instance of this module that can be used to generate middleware + * @param cfg + */ export function createValidator (cfg? : ExpressJoiConfig): ExpressJoiInstance +/** + * These are the named properties on an express.Request that this module can + * validate, e.g "body" or "query" + */ +export enum ContainerTypes { + Body = 'body', + Query = 'query', + Headers = 'headers', + Fields = 'fields', + Params = 'params' +} + +/** + * Use this in you express error handler if you've set *passError* to true + * when calling *createValidator* + */ +export interface ExpressJoiError extends Joi.ValidationResult { + type: ContainerTypes +} + +/** + * A schema that developers should extend to strongly type the properties + * (query, body, etc.) of incoming express.Request passed to a request handler. + */ +export type ValidatedRequestSchema = Record + +/** + * Use this in conjunction with *ValidatedRequestSchema* instead of + * express.Request for route handlers. This ensures *req.query*, + * *req.body* and others are strongly typed using your + * *ValidatedRequestSchema* + */ export interface ValidatedRequest extends express.Request { - body: T['body'] - query: T['query'] - headers: T['headers'] - params: T['params'] - fields: T['fields'] + body: T[ContainerTypes.Body] + query: T[ContainerTypes.Query] + headers: T[ContainerTypes.Headers] + params: T[ContainerTypes.Params] + fields: T[ContainerTypes.Fields] originalBody: any originalQuery: any originalHeaders: IncomingHttpHeaders @@ -17,26 +53,28 @@ export interface ValidatedRequest extends expr originalFields: any } -export interface ValidatedRequestSchema { - body?: any - query?: any - headers?: any - params?: any - fields?: any -} - +/** + * Configuration options supportef by *createValidator(config)* + */ export interface ExpressJoiConfig { joi?: typeof Joi statusCode?: number passError?: boolean } +/** + * Configuration options supported by middleware, e.g *validator.body(config)* + */ export interface ExpressJoiContainerConfig { joi?: Joi.ValidationOptions statusCode?: number passError?: boolean } +/** + * A validator instance that can be used to generate middleware. Is returned by + * calling *createValidator* + */ export interface ExpressJoiInstance { body (schema: Joi.Schema, cfg?: ExpressJoiContainerConfig): express.RequestHandler query (schema: Joi.Schema, cfg?: ExpressJoiContainerConfig): express.RequestHandler diff --git a/package.json b/package.json index 3db0afe..f22f723 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "validate express application inputs and parameters using joi", "main": "express-joi-validation.js", "scripts": { - "precommit": "npm run format && npm test", + "precommit": "npm run format && npm test && git add .", "format": "prettier --no-semi --single-quote --write index.test.js example/**/*.js example/**/*.ts express-joi-validation.js", "unit": "mocha *.test.js", "ts-test": "tsc express-joi-validation.d.ts --target es5 --module commonjs --noEmit", From 10e9dfbba85b83344f77964359027f33f1aced0b Mon Sep 17 00:00:00 2001 From: Evan Shortiss Date: Thu, 27 Jun 2019 14:28:18 -0700 Subject: [PATCH 05/10] chore: update linting --- package.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f22f723..f74dd58 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,6 @@ "description": "validate express application inputs and parameters using joi", "main": "express-joi-validation.js", "scripts": { - "precommit": "npm run format && npm test && git add .", - "format": "prettier --no-semi --single-quote --write index.test.js example/**/*.js example/**/*.ts express-joi-validation.js", "unit": "mocha *.test.js", "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", @@ -46,6 +44,7 @@ "express-formidable": "~1.0.0", "husky": "~1.0.1", "joi-extract-type": "~15.0.0", + "lint-staged": "~8.2.1", "lodash": "~4.17.4", "mocha": "~5.2.0", "mocha-lcov-reporter": "~1.3.0", @@ -74,5 +73,16 @@ "bugs": { "url": "https://github.com/evanshortiss/express-joi-validation/issues" }, - "homepage": "https://github.com/evanshortiss/express-joi-validation#readme" + "homepage": "https://github.com/evanshortiss/express-joi-validation#readme", + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.js,*.ts": [ + "prettier --no-semi --single-quote", + "git add" + ] + } } From 141f009a037acc13614b4b704190eb58c574887f Mon Sep 17 00:00:00 2001 From: Evan Shortiss Date: Thu, 27 Jun 2019 14:53:42 -0700 Subject: [PATCH 06/10] chore: update example structure --- example/javascript/router.js | 112 +++++++++++++++++++++++++++++++++++ example/javascript/server.js | 32 ++++++++++ example/javascript/users.js | 12 ++++ example/router.js | 87 --------------------------- example/server.js | 29 --------- example/users.js | 12 ---- package.json | 10 +++- 7 files changed, 163 insertions(+), 131 deletions(-) create mode 100644 example/javascript/router.js create mode 100644 example/javascript/server.js create mode 100644 example/javascript/users.js delete mode 100644 example/router.js delete mode 100644 example/server.js delete mode 100644 example/users.js diff --git a/example/javascript/router.js b/example/javascript/router.js new file mode 100644 index 0000000..bd97b8a --- /dev/null +++ b/example/javascript/router.js @@ -0,0 +1,112 @@ +'use strict' + +const route = (module.exports = require('express').Router()) +const users = require('./users') +const Joi = require('joi') +const _ = require('lodash') +const validator = require('../index.js')({}) + +/** + * This "GET /:id" endpoint is used to query users by their ID + * Try accessing http://localhost:8080/users/1001 to see it in action. + * Now try http://localhost:8080/users/bananas - it will fail since the ID must be an integer + */ +const paramsSchema = Joi.object({ + id: Joi.number().required() +}) + +route.get('/:id', validator.params(paramsSchema), (req, res) => { + console.log(`\nGetting user by ID ${req.params.id}.`) + console.log( + `req.params was ${JSON.stringify(req.originalParams)} before validation` + ) + console.log(`req.params is ${JSON.stringify(req.params)} after validation`) + console.log('note that the ID was correctly cast to an integer') + + const u = _.find(users, { id: req.params.id }) + + if (u) { + res.json(u) + } else { + res.status(404).json({ + message: `no user exists with id "${req.params.id}"` + }) + } +}) + +/** + * This "GET /" endpoint is used to query users by a querystring + * Try accessing http://localhost:8080/users?name=j&age=25 to get users that are 25 or with a name containing a "j". + * Now try http://localhost:8080/users - it will fail since name is required + */ +const querySchema = Joi.object({ + name: Joi.string() + .required() + .min(1) + .max(10), + age: Joi.number() + .integer() + .min(1) + .max(120) +}) + +route.get('/', validator.query(querySchema), (req, res) => { + console.log(`\nGetting users for query ${JSON.stringify(req.query)}.`) + console.log( + `req.query was ${JSON.stringify(req.originalQuery)} before validation` + ) + console.log(`req.query is ${JSON.stringify(req.query)} after validation`) + console.log( + 'note that the age was correctly cast to an integer if provided\n' + ) + + res.json( + _.filter(users, u => { + return ( + _.includes(u.name, req.query.name) || + (req.query.age && u.age === req.query.age) + ) + }) + ) +}) + +/** + * This "POST /" endpoint is used to create new users + * POST to http://localhost:8080/users with '{"name": "jane", "age": "26"}' to see it work + * Now try posting '{"name": "jane", "age": 1000}' - it will fail since the age is above 120 + */ +const bodySchema = Joi.object({ + name: Joi.string() + .required() + .min(1) + .max(10), + age: Joi.number() + .integer() + .required() + .min(1) + .max(120) +}) + +route.post( + '/', + require('body-parser').json(), + validator.body(bodySchema), + (req, res) => { + console.log(`\Creating user with data ${JSON.stringify(req.body)}.`) + console.log( + `req.body was ${JSON.stringify(req.originalBody)} before validation` + ) + console.log(`req.body is ${JSON.stringify(req.body)} after validation`) + console.log( + 'note that the age was correctly cast to an integer if it was a string\n' + ) + + // Generate data required for insert (new id is incremented from previous max) + const prevMaxId = _.maxBy(users, u => u.id).id + const data = Object.assign({}, req.body, { id: prevMaxId + 1 }) + + users.push(data) + + res.json({ message: 'created user', data: data }) + } +) diff --git a/example/javascript/server.js b/example/javascript/server.js new file mode 100644 index 0000000..6bdefbe --- /dev/null +++ b/example/javascript/server.js @@ -0,0 +1,32 @@ +'use strict' + +process.title = 'express-joi-validation' + +const port = 8080 + +const app = require('express')() +const Joi = require('joi') +const validator = require('../index.js')({}) + +const headerSchema = Joi.object({ + host: Joi.string().required(), + 'user-agent': Joi.string().required() +}) + +app.use(validator.headers(headerSchema)) + +app.use('/users', require('./router')) + +app.listen(port, 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.` + ) +}) diff --git a/example/javascript/users.js b/example/javascript/users.js new file mode 100644 index 0000000..e8fe4c0 --- /dev/null +++ b/example/javascript/users.js @@ -0,0 +1,12 @@ +'use strict' + +module.exports = [ + { id: 1000, name: 'anne, a.', age: 25 }, + { id: 1001, name: 'barry, a.', age: 52 }, + { id: 1002, name: 'clare, a.', age: 25 }, + { id: 1003, name: 'joe, a.', age: 67 }, + { id: 1004, name: 'anne, b.', age: 47 }, + { id: 1005, name: 'barry, b.', age: 80 }, + { id: 1006, name: 'clare, b.', age: 28 }, + { id: 1007, name: 'joe, b.', age: 15 } +] diff --git a/example/router.js b/example/router.js deleted file mode 100644 index ba00f0f..0000000 --- a/example/router.js +++ /dev/null @@ -1,87 +0,0 @@ -'use strict'; - - -const route = module.exports = require('express').Router(); -const users = require('./users'); -const Joi = require('joi'); -const _ = require('lodash'); -const validator = require('../index.js')({}); - - - -/** - * This "GET /:id" endpoint is used to query users by their ID - * Try accessing http://localhost:8080/users/1001 to see it in action. - * Now try http://localhost:8080/users/bananas - it will fail since the ID must be an integer - */ -const paramsSchema = Joi.object({ - id: Joi.number().required() -}); - -route.get('/:id', validator.params(paramsSchema), (req, res) => { - console.log(`\nGetting user by ID ${req.params.id}.`); - console.log(`req.params was ${JSON.stringify(req.originalParams)} before validation`); - console.log(`req.params is ${JSON.stringify(req.params)} after validation`); - console.log('note that the ID was correctly cast to an integer'); - - const u = _.find(users, {id: req.params.id}); - - if (u) { - res.json(u); - } else { - res.status(404).json({ - message: `no user exists with id "${req.params.id}"` - }); - } -}); - - - -/** - * This "GET /" endpoint is used to query users by a querystring - * Try accessing http://localhost:8080/users?name=j&age=25 to get users that are 25 or with a name containing a "j". - * Now try http://localhost:8080/users - it will fail since name is required - */ -const querySchema = Joi.object({ - name: Joi.string().required().min(1).max(10), - age: Joi.number().integer().min(1).max(120) -}); - -route.get('/', validator.query(querySchema), (req, res) => { - console.log(`\nGetting users for query ${JSON.stringify(req.query)}.`); - console.log(`req.query was ${JSON.stringify(req.originalQuery)} before validation`); - console.log(`req.query is ${JSON.stringify(req.query)} after validation`); - console.log('note that the age was correctly cast to an integer if provided\n'); - - res.json( - _.filter(users, (u) => { - return _.includes(u.name, req.query.name) || req.query.age && u.age === req.query.age; - }) - ); -}); - - -/** - * This "POST /" endpoint is used to create new users - * POST to http://localhost:8080/users with '{"name": "jane", "age": "26"}' to see it work - * Now try posting '{"name": "jane", "age": 1000}' - it will fail since the age is above 120 - */ -const bodySchema = Joi.object({ - name: Joi.string().required().min(1).max(10), - age: Joi.number().integer().required().min(1).max(120) -}); - -route.post('/', require('body-parser').json(), validator.body(bodySchema), (req, res) => { - console.log(`\Creating user with data ${JSON.stringify(req.body)}.`); - console.log(`req.body was ${JSON.stringify(req.originalBody)} before validation`); - console.log(`req.body is ${JSON.stringify(req.body)} after validation`); - console.log('note that the age was correctly cast to an integer if it was a string\n'); - - // Generate data required for insert (new id is incremented from previous max) - const prevMaxId = _.maxBy(users, (u) => u.id).id; - const data = Object.assign({}, req.body, {id: prevMaxId + 1}); - - users.push(data); - - res.json({message: 'created user', data: data}); -}); diff --git a/example/server.js b/example/server.js deleted file mode 100644 index 86ce7f4..0000000 --- a/example/server.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -process.title = 'express-joi-validation'; - -const port = 8080; - -const app = require('express')(); -const Joi = require('joi'); -const validator = require('../index.js')({}); - -const headerSchema = Joi.object({ - 'host': Joi.string().required(), - 'user-agent': Joi.string().required() -}); - - -app.use(validator.headers(headerSchema)); - -app.use('/users', require('./router')); - -app.listen(port, (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.`); -}); diff --git a/example/users.js b/example/users.js deleted file mode 100644 index 1118e79..0000000 --- a/example/users.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -module.exports = [ - {id: 1000, name: 'anne, a.', age: 25}, - {id: 1001, name: 'barry, a.', age: 52}, - {id: 1002, name: 'clare, a.', age: 25}, - {id: 1003, name: 'joe, a.', age: 67}, - {id: 1004, name: 'anne, b.', age: 47}, - {id: 1005, name: 'barry, b.', age: 80}, - {id: 1006, name: 'clare, b.', age: 28}, - {id: 1007, name: 'joe, b.', age: 15} -]; diff --git a/package.json b/package.json index f74dd58..bcb2259 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "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": "nodemon example/javascript/server.js", "example-ts": "tsc example/typescript/server.ts && node example/typescript/server.js", "coveralls": "npm run cover && cat coverage/lcov.info | coveralls" }, @@ -80,8 +80,12 @@ } }, "lint-staged": { - "*.js,*.ts": [ - "prettier --no-semi --single-quote", + "**/*.js": [ + "prettier --no-semi --single-quote --write", + "git add" + ], + "**/*.ts": [ + "prettier --no-semi --single-quote --write", "git add" ] } From ad2c1e397d8f64238134ed2bf427a8988dd2a88a Mon Sep 17 00:00:00 2001 From: Evan Shortiss Date: Thu, 27 Jun 2019 14:58:25 -0700 Subject: [PATCH 07/10] docs: restructure readme --- README.md | 172 +++++++++++++++++++++++++++--------------------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index a5ce3f2..39aa5f5 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,92 @@ app.get( ) ``` +## API + +### Structure + +* module (express-joi-validation) + * [createValidator(config)](#createvalidatorconfig) + * [query(options)](#validatorqueryschema-options) + * [body(options)](#validatorbodyschema-options) + * [headers(options)](#headersschema-options) + * [params(options)](#validatorparamsschema-options) + * [fields(options)](#validatorfieldsschema-options) + + +### createValidator(config) +Creates a validator. Supports the following options: + +* passError (default: `false`) - Passes validation errors to the express error +hander using `next(err)` when `true` +* statusCode (default: `400`) - The status code used when validation fails and +`passError` is `false`. + +#### validator.query(schema, [options]) +Creates a middleware instance that will validate the `req.query` for an +incoming request. Can be passed `options` that override the config passed +when the validator was created. + +Supported options are: + +* joi - Custom options to pass to `Joi.validate`. +* passError - Same as above. +* statusCode - Same as above. + +#### validator.body(schema, [options]) +Creates a middleware instance that will validate the `req.body` for an incoming +request. Can be passed `options` that override the options passed when the +validator was created. + +Supported options are the same as `validator.query`. + +#### validator.headers(schema, [options]) +Creates a middleware instance that will validate the `req.headers` for an +incoming request. Can be passed `options` that override the options passed +when the validator was created. + +Supported options are the same as `validator.query`. + +#### validator.params(schema, [options]) +Creates a middleware instance that will validate the `req.params` for an +incoming request. Can be passed `options` that override the options passed +when the validator was created. + +Supported options are the same as `validator.query`. + +#### validator.response(schema, [options]) +Creates a middleware instance that will validate the outgoing response. +Can be passed `options` that override the options passed when the instance was +created. + +Supported options are the same as `validator.query`. + +#### validator.fields(schema, [options]) +Creates 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 validator 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: + +```js +const schema = Joi.object({ + id: Joi.number().integer().required() +}); + +// INCORRECT +app.use(validator.params(schema)); +app.get('/orders/:id', (req, res, next) => { + // The "id" parameter will NOT have been validated here! +}); + +// CORRECT +app.get('/orders/:id', validator.params(schema), (req, res, next) => { + // This WILL have a validated "id" +}) +``` + +Supported options are the same as `validator.query`. ## Behaviours @@ -257,89 +343,3 @@ app.use((err: any|ExpressJoiError, req: express.Request, res: express.Response, }) ``` -## API - -### Structure - -* module (express-joi-validation) - * createValidator(config) - * query(options) - * body(options) - * params(options) - * headers(options) - * fields(options) - - -### createValidator(config) -Creates a validator. Supports the following options: - -* passError (default: `false`) - Passes validation errors to the express error -hander using `next(err)` when `true` -* statusCode (default: `400`) - The status code used when validation fails and -`passError` is `false`. - -#### validator.query(schema, [options]) -Creates a middleware instance that will validate the `req.query` for an -incoming request. Can be passed `options` that override the config passed -when the validator was created. - -Supported options are: - -* joi - Custom options to pass to `Joi.validate`. -* passError - Same as above. -* statusCode - Same as above. - -#### validator.body(schema, [options]) -Creates a middleware instance that will validate the `req.body` for an incoming -request. Can be passed `options` that override the options passed when the -validator was created. - -Supported options are the same as `validator.query`. - -#### validator.headers(schema, [options]) -Creates a middleware instance that will validate the `req.headers` for an -incoming request. Can be passed `options` that override the options passed -when the validator was created. - -Supported options are the same as `validator.query`. - -#### validator.params(schema, [options]) -Creates a middleware instance that will validate the `req.params` for an -incoming request. Can be passed `options` that override the options passed -when the validator was created. - -Supported options are the same as `validator.query`. - -#### validator.response(schema, [options]) -Creates a middleware instance that will validate the outgoing response. -Can be passed `options` that override the options passed when the instance was -created. - -Supported options are the same as `validator.query`. - -#### validator.fields(schema, [options]) -Creates 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 validator 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: - -```js -const schema = Joi.object({ - id: Joi.number().integer().required() -}); - -// INCORRECT -app.use(validator.params(schema)); -app.get('/orders/:id', (req, res, next) => { - // The "id" parameter will NOT have been validated here! -}); - -// CORRECT -app.get('/orders/:id', validator.params(schema), (req, res, next) => { - // This WILL have a validated "id" -}) -``` - -Supported options are the same as `validator.query`. From 74b261dc63de7e87b153e114fda4039ff9c7b855 Mon Sep 17 00:00:00 2001 From: Evan Shortiss Date: Thu, 27 Jun 2019 15:00:08 -0700 Subject: [PATCH 08/10] docs: remove link to examples --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 39aa5f5..dc16554 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ npm i @types/hapi__joi --save-dev ## Example -A JavaScript and TypeScript example can be found in the -[example/](/tree/master/example) folder of this repository. +A JavaScript and TypeScript example can be found in the `example/` folder of +this repository. ## Usage (JavaScript) From b78ecfbba5537919f9466d1d9ddb824f7f42fc95 Mon Sep 17 00:00:00 2001 From: Evan Shortiss Date: Thu, 27 Jun 2019 15:06:10 -0700 Subject: [PATCH 09/10] docs: quicklinks --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index dc16554..c61da10 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,17 @@ e.g `req.originalQuery` * Uses `peerDependencies` to get a Joi instance of your choosing instead of using a fixed version. +## Quick Links + +* [API](#api) +* [Usage (JavaScript)](#usage-javascript) +* [Usage (TypeScript)](#usage-typescript) +* [Behaviours](#behaviours) + * [Joi Versioning](#joi-versioning) + * [Validation Ordering](#validation-ordering) + * [Error Handling](#error-handling) + * [Joi Options](#joi-options) + * [Custom Express Error Handler](#custom-express-error-handler) ## Install From 89326d8bbd3de6f58f0bfc3d5a95d918879f1a61 Mon Sep 17 00:00:00 2001 From: Evan Shortiss Date: Thu, 27 Jun 2019 15:12:16 -0700 Subject: [PATCH 10/10] chore: add keywords --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bcb2259..fb2e93d 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,10 @@ "sanatize", "sanatise", "input", - "parameter" + "parameter", + "typescript", + "ts", + "tsc" ], "author": "Evan Shortiss", "license": "MIT",