├── .gitignore ├── hapi.js ├── .travis.yml ├── tslint.json ├── tsconfig.json ├── package.json ├── LICENSE ├── index.test-d.ts └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test.js 3 | -------------------------------------------------------------------------------- /hapi.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@hapi/hapi'); 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | 5 | cache: npm 6 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "dtslint/dtslint.json", 3 | "rules": { 4 | "no-redundant-jsdoc": false, 5 | "max-line-length": false, 6 | "no-unnecessary-generics": false, 7 | "strict-export-declare-modifiers": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "paths": { 7 | "typesafe-hapi": ["./"], 8 | "hapi": ["./"] 9 | }, 10 | "lib": ["es2015"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typesafe-hapi", 3 | "description": "A fork of hapi that produces typed results TypeScript", 4 | "version": "4.0.1", 5 | "main": "hapi.js", 6 | "types": "index.d.ts", 7 | "repository": "https://github.com/mmiszy/typesafe-hapi.git", 8 | "author": "Michal Miszczyszyn ", 9 | "license": "MIT", 10 | "scripts": { 11 | "test": "dtslint" 12 | }, 13 | "keywords": [ 14 | "hapi", 15 | "typescript", 16 | "validation" 17 | ], 18 | "dependencies": { 19 | "@hapi/hapi": "18.4.0", 20 | "@types/hapi__boom": "7.4.1", 21 | "@types/hapi__catbox": "10.2.2", 22 | "@types/hapi__iron": "5.1.0", 23 | "@types/hapi__mimos": "4.1.0", 24 | "@types/hapi__podium": "3.4.0", 25 | "@types/hapi__shot": "4.1.0", 26 | "@types/node": "*" 27 | }, 28 | "peerDependencies": { 29 | "typesafe-joi": "2.0.6" 30 | }, 31 | "devDependencies": { 32 | "@hapi/joi": "15.1.0", 33 | "dtslint": "0.9.8", 34 | "typesafe-joi": "2.0.6", 35 | "typescript": "3.6.4" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 - Michał Miszczyszyn 4 | 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /index.test-d.ts: -------------------------------------------------------------------------------- 1 | import Hapi from 'hapi'; 2 | import Joi from 'typesafe-joi'; 3 | 4 | const server = new Hapi.Server(); 5 | 6 | /** 7 | * User's codebase 8 | */ 9 | const payloadSchema = Joi.object({ 10 | user: Joi.object({ 11 | name: Joi.string().required(), 12 | email: Joi.string().required(), 13 | }).required(), 14 | }).required(); 15 | 16 | const querySchema = Joi.object({ 17 | search: Joi.string().optional().allow('', null), 18 | }).required(); 19 | 20 | const paramsSchema = Joi.object({ 21 | id: Joi.number().required() 22 | }).required(); 23 | 24 | const responseSchema = Joi.object({ 25 | id: Joi.number().required(), 26 | name: Joi.string().required(), 27 | email: Joi.string().required(), 28 | search: Joi.string().optional().allow(null), 29 | }).required(); 30 | 31 | server.route({ 32 | method: 'POST', 33 | path: '/:id', 34 | options: { 35 | validate: { 36 | payload: payloadSchema, 37 | query: querySchema, 38 | params: paramsSchema, 39 | }, 40 | response: { 41 | schema: responseSchema, 42 | }, 43 | }, 44 | handler(request) { 45 | // type of `payload` is automatically inferred based on `options.validate.payload` schema 46 | const payload = request.payload; // $ExpectType { user: { name: string; email: string; } & {}; } & {} 47 | const query = request.query; // $ExpectType {} & { search?: string | null | undefined; } 48 | const params = request.params; // $ExpectType { id: number; } & {} 49 | 50 | // return type is also automatically inferred based on `options.response.schema` 51 | return { 52 | id: params.id, 53 | name: payload.user.name, // $ExpectType string 54 | email: payload.user.email, // $ExpectType string 55 | search: query.search, // $ExpectType string | null | undefined 56 | }; 57 | }, 58 | }); 59 | 60 | server.route({ 61 | method: 'GET', 62 | path: '/health-check', 63 | options: { 64 | description: 'Health check endpoint', 65 | tags: ['api'], 66 | }, 67 | handler(_request) { 68 | return null; 69 | }, 70 | }); 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # typesafe-hapi 2 | 3 | typesafe-hapi is a fork of [hapi](https://github.com/hapijs/hapi) which aims to improve typesafety. More precisely, this is a fork of [@types/hapi__hapi](https://www.npmjs.com/package/@types/hapi__hapi) because it has just redefined the essential APIs of hapi. 4 | 5 | typesafe-hapi currently matches the API of hapi 18.3.x. It was tested with TypeScript 3.4.5. 6 | 7 | ## How it works 8 | `typesafe-hapi` uses `typesafe-joi` to infer correct type from Joi schemas automatically. It uses possesed type information to typecheck: 9 | 10 | * request.query 11 | * request.payload 12 | * request.params 13 | * response 14 | 15 | ## Example 16 | 17 | ```ts 18 | const payloadSchema = Joi.object({ 19 | user: Joi.object({ 20 | name: Joi.string().required(), 21 | email: Joi.string().required(), 22 | }).required(), 23 | }).required(); 24 | 25 | const querySchema = Joi.object({ 26 | search: Joi.string().optional().allow('', null), 27 | }).required(); 28 | 29 | const paramsSchema = Joi.object({ 30 | id: Joi.number().required() 31 | }).required(); 32 | 33 | const responseSchema = Joi.object({ 34 | id: Joi.number().required(), 35 | name: Joi.string().required(), 36 | email: Joi.string().required(), 37 | search: Joi.string().optional().allow(null), 38 | }).required(); 39 | 40 | server.route({ 41 | method: 'POST', 42 | path: '/:id', 43 | options: { 44 | validate: { 45 | payload: payloadSchema, 46 | query: querySchema, 47 | params: paramsSchema, 48 | }, 49 | response: { 50 | schema: responseSchema, 51 | }, 52 | }, 53 | handler(request) { 54 | // type of `payload` is automatically inferred based on `options.validate.payload` schema 55 | const payload = request.payload; // { user: { name: string; email: string; }; } 56 | const query = request.query; // { search?: string | null | undefined; } 57 | const params = request.params; // { id: number; } 58 | 59 | // return type is also typechecked based on `options.response.schema` 60 | return { 61 | id: params.id, 62 | name: payload.user.name, // string 63 | email: payload.user.email, // string 64 | search: query.search, // string | null | undefined 65 | }; 66 | }, 67 | }); 68 | ``` 69 | 70 | Neat, huh? See more examples in [index.test-d.ts](https://github.com/mmiszy/typesafe-hapi/blob/master/index.test-d.ts). 71 | 72 | ## Usage 73 | 74 | Import and use hapi from `typesafe-hapi`: 75 | 76 | ```typescript 77 | import * as Hapi from 'typesafe-hapi' 78 | ``` 79 | 80 | In order to avoid any compatibility issues, and to be able to use existing packages and plugins easily, you should create an alias for `typesafe-hapi` and rename it to just `hapi`. In your `tsconfig.json`: 81 | 82 | ```json 83 | { 84 | "compilerOptions": { 85 | // … other options 86 | "paths": { 87 | "hapi": ["node_modules/typesafe-hapi"], 88 | "joi": ["node_modules/typesafe-joi"] 89 | } 90 | } 91 | } 92 | ``` 93 | --------------------------------------------------------------------------------