├── .coveragebadgesrc ├── .eslintrc.json ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── _index.js ├── badges └── coverage.svg ├── client.js ├── index.js ├── package-lock.json ├── package.json └── src ├── __tests__ ├── app.js ├── query-params.js └── url-to-regex.js ├── app.js ├── query-params.js └── url-to-regex.js /.coveragebadgesrc: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./coverage/coverage-summary.json", 3 | "attribute": "total.statements.pct" 4 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "jest/globals": true 7 | }, 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:node/recommended" 11 | ], 12 | "globals": { 13 | "Atomics": "readonly", 14 | "SharedArrayBuffer": "readonly" 15 | }, 16 | "parserOptions": { 17 | "ecmaVersion": 11 18 | }, 19 | "plugins": [ 20 | "jest" 21 | ], 22 | "rules": { 23 | "semi": [ 24 | "error", 25 | "always" 26 | ], 27 | "quotes": [ 28 | "error", 29 | "double" 30 | ], 31 | "jest/no-disabled-tests": "warn", 32 | "jest/no-focused-tests": "error", 33 | "jest/no-identical-title": "error", 34 | "jest/prefer-to-have-length": "warn", 35 | "jest/valid-expect": "error", 36 | "node/no-unpublished-require": "off", 37 | "node/no-extraneous-require": "off" 38 | } 39 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .vscode 4 | dist 5 | .DS_Store -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at christoffer.noring@microsoft.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This project welcomes contributions and suggestions. 2 | 3 | Any feature suggestion, bugs, tests. Please raise an issue at `https://github.com/softchris/mini-web/issues`. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Chris Noring 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![The MIT License](https://img.shields.io/badge/license-MIT-orange.svg?color=blue&style=flat-square)](http://opensource.org/licenses/MIT) 2 | ![Coverage](./badges/coverage.svg) 3 | [![npm version](https://badge.fury.io/js/quarkhttp.svg)](https://www.npmjs.com/package/quarkhttp) 4 | [![npm downloads](https://img.shields.io/npm/dm/quarkhttp?color=blue&label=npm%20downloads&style=flat-square)](https://www.npmjs.com/package/quarkhttp) 5 | 6 | ## Table of Contents 7 | 8 | - [About](#about) 9 | - [Install](#install) 10 | - [Features](#features) 11 | - [Create an app](#create-an-app) 12 | 13 | ## About 14 | 15 | This is a minimalistic Web framework for Node.js. It helps you create RESTful APIs. 16 | 17 | The idea is to have 0 dependencies while still have all the functionality you would expect from bigger frameworks like Express, Koa, Fastify etc with just a fraction of the footprint. 18 | 19 | ## Install 20 | 21 | ```bash 22 | npm install quarkhttp 23 | ``` 24 | 25 | ## Features 26 | 27 | - Create routes supporting GET, POST, PUT, DELETE HTTP Verbs. There are convenience methods for this: 28 | 29 | ```javascript 30 | app.get('', (req, res) => {}) 31 | app.post('', (req, res) => {}) 32 | app.put('', (req, res) => {}) 33 | app.delete('', (req, res) => {}) 34 | ``` 35 | 36 | - Reads posted body to either Text or JSON. Use method `bodyParse(method)` to change how the body is parsed. Valid input values `json` or `text`. 37 | - Has middleware that you can run before handling the actual request. Can be used for Authentication for example. 38 | 39 | ```javascript 40 | app.get('/products', (req, res, next) => { 41 | if (req.headers['authorization'] === 'abc123') { 42 | next(); 43 | } else { 44 | res.statusCode = 401; 45 | res.send('Not allowed') 46 | } 47 | }) 48 | ``` 49 | 50 | - Handles route parameters and query parameters 51 | 52 | **Router parameters** 53 | 54 | ```javascript 55 | app.get('/products/:id', (req, res) => { 56 | console.log(req.params) // for route /products/1 { id: "1" } 57 | }) 58 | ``` 59 | 60 | **Query parameters** 61 | 62 | ```javascript 63 | app.get('/products/', (req, res) => { 64 | console.log(req.query) // for route /products?page=1&pageSize=20 { page: "1", pageSize: "20"} 65 | }) 66 | ``` 67 | 68 | ## Create an app 69 | 70 | ```javascript 71 | const quark = require('quarkhttp'); 72 | 73 | const app = quark(); 74 | 75 | // ROUTE PARAMETERS 76 | app.get("/products/:id", (req, res) => { 77 | console.log("query params", req.query); 78 | console.log('req.params', req.params); 79 | res.send("product id"); 80 | }); 81 | 82 | app.get('/products', (req, res) => { 83 | console.log('query params', req.query) 84 | res.send('text'); 85 | }) 86 | 87 | // POST 88 | app.post('/products', (req,res) => { 89 | console.info('body', req.body) 90 | res.json(req.body); 91 | }) 92 | 93 | // PUT 94 | app.put('/products', (req,res) => { 95 | console.info('body', req.body) 96 | res.json(req.body); 97 | }) 98 | 99 | // MIDDLEWARE 100 | app.get('/orders', (req, res, next) => { 101 | if (req.headers['authorization'] === 'abc123') { 102 | console.log('next', next) 103 | next() 104 | } else { 105 | res.statusCode = 401; 106 | res.send('Not allowed') 107 | } 108 | }, (req, res) => { 109 | res.send('Protected route'); 110 | }) 111 | 112 | // Starts listening to requests 113 | app.listen(3000, () => { 114 | console.log('Server running on 3000'); 115 | }) 116 | ``` -------------------------------------------------------------------------------- /_index.js: -------------------------------------------------------------------------------- 1 | const pico = require('./app'); 2 | 3 | const app = pico(); 4 | app.get("/products/:id", (req, res) => { 5 | console.log("query params", req.query); 6 | console.log('req.params', req.params); 7 | res.send("product id"); 8 | }); 9 | 10 | app.get('/products', (req, res) => { 11 | console.log('query params', req.query) 12 | res.send('text'); 13 | }) 14 | 15 | app.post('/products', (req,res) => { 16 | console.info('body', req.body) 17 | res.json(req.body); 18 | }) 19 | 20 | // TODO make this work 21 | app.get('/orders', (req, res, next) => { 22 | if (req.headers['authorization'] === 'abc123') { 23 | console.log('next', next) 24 | next() 25 | } else { 26 | res.statusCode = 401; 27 | res.send('Not allowed') 28 | } 29 | }, (req, res) => { 30 | res.send('Protected route'); 31 | }) 32 | 33 | // TODO query parameters 34 | // TODO middleware function middleware(req, res, next) {} 35 | 36 | app.listen(3000, () => { 37 | console.log('Server running on 3000'); 38 | }) -------------------------------------------------------------------------------- /badges/coverage.svg: -------------------------------------------------------------------------------- 1 | CoverageCoverage94%94% -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const options = { 3 | hostname: 'localhost', 4 | port: 3000, 5 | path: '/orders', 6 | method: 'get' 7 | } 8 | 9 | http.get(options, (res) => { 10 | res.on('data', (chunk) => { 11 | console.log('chunk', ""+ chunk); 12 | }) 13 | }) -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/app.js'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quarkhttp", 3 | "version": "1.0.0", 4 | "engines": { 5 | "node": ">=10.0.0" 6 | }, 7 | "files": [ 8 | "README.md", 9 | "index.js", 10 | "src/app.js", 11 | "src/query-params.js", 12 | "src/url-to-regex.js" 13 | ], 14 | "description": "Minimalistic web framework with 0 dependencies", 15 | "main": "./src/app.js", 16 | "scripts": { 17 | "test": "jest", 18 | "test:watch": "jest --watchAll", 19 | "test:coverage": "jest --coverage", 20 | "premake-badge": "$(npm bin)/jest --coverage", 21 | "make-badge": "$(npm bin)/coverage-badges", 22 | "toc": "npx markdown-toc README.md", 23 | "lint": "npx eslint ./src/**" 24 | }, 25 | "jest": { 26 | "coverageReporters": [ 27 | "text", 28 | "lcov", 29 | "json-summary", 30 | "cobertura" 31 | ] 32 | }, 33 | "keywords": [ 34 | "quarkhttp", 35 | "framework", 36 | "web", 37 | "http", 38 | "rest", 39 | "restful", 40 | "router", 41 | "app", 42 | "api" 43 | ], 44 | "author": "Chris Noring (https://softchris.github.io)", 45 | "contributors": [ 46 | { 47 | "name": "chris noring", 48 | "url": "https://softchris.github.io" 49 | } 50 | ], 51 | "repository": { 52 | "type": "git", 53 | "url": "https://github.com/softchris/mini-web.git" 54 | }, 55 | "homepage": "https://github.com/softchris/mini-web", 56 | "bugs": { 57 | "url": "https://github.com/softchris/mini-web/issues" 58 | }, 59 | "license": "MIT", 60 | "dependencies": {}, 61 | "devDependencies": { 62 | "jest": "^26.0.1", 63 | "supertest": "^4.0.2", 64 | "coverage-badges": "^1.0.4", 65 | "eslint": "^7.0.0", 66 | "eslint-plugin-jest": "^23.13.1", 67 | "eslint-plugin-node": "^11.1.0", 68 | "markdown-toc": "^1.2.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/__tests__/app.js: -------------------------------------------------------------------------------- 1 | const server = require("../app")(); 2 | const supertest = require("supertest"); 3 | const request = supertest(server._server); 4 | 5 | describe("server", () => { 6 | const products = [ 7 | { 8 | id: 1, 9 | name: "test", 10 | }, 11 | ]; 12 | 13 | beforeAll(() => { 14 | server.get("/products", (req, res) => { 15 | res.json(products); 16 | }); 17 | 18 | server.get("/protected", (req, res, next) => { 19 | if (req.headers["authorization"] === "abc123") { 20 | next(); 21 | } else { 22 | res.statusCode = 401; 23 | res.send("Not allowed"); 24 | } 25 | }, (req, res) => { 26 | res.send("protected route"); 27 | }); 28 | 29 | server.get("/orders/:id/items/:item", (req, res) => { 30 | res.json(req.params); 31 | }); 32 | 33 | server.delete("/orders/:id", (req, res) => { 34 | res.json(req.params); 35 | }); 36 | 37 | server.get("/orders", (req, res) => { 38 | res.json(req.query); 39 | }); 40 | 41 | server.post("/products", (req,res) => { 42 | res.json(req.body); 43 | }); 44 | 45 | server.put("/products", (req, res) => { 46 | res.json(req.body); 47 | }); 48 | }); 49 | 50 | afterAll(async (done) => { 51 | server._server.close(() => { 52 | console.log("server closed!"); 53 | done(); 54 | }); 55 | }); 56 | test("GET should return 1 product", async(done) => { 57 | const res = await request.get("/products"); 58 | expect(res.status).toBe(200); 59 | expect(res.body).toEqual(products); 60 | done(); 61 | }); 62 | 63 | test("POST should return the posted body", async(done) => { 64 | const res = await request 65 | .post("/products") 66 | .send({ name : "cucumber" }); 67 | expect(res.body).toEqual({ name : "cucumber" }); 68 | done(); 69 | }); 70 | 71 | test("PUT should return the posted body", async (done) => { 72 | const res = await request.put("/products").send({ name: "cucumber" }); 73 | expect(res.body).toEqual({ name: "cucumber" }); 74 | done(); 75 | }); 76 | 77 | test("GET should return query params", async (done) => { 78 | const res = await request.get("/orders?page=1&pageSize=200"); 79 | expect(res.status).toBe(200); 80 | expect(res.body).toEqual({ 81 | page: "1", 82 | pageSize: "200" 83 | }); 84 | done(); 85 | }); 86 | 87 | test("GET should return correct route params", async(done) => { 88 | const res = await request.get("/orders/1/items/2"); 89 | expect(res.status).toBe(200); 90 | expect(res.body).toEqual({ 91 | id: "1", 92 | item: "2" 93 | }); 94 | done(); 95 | }); 96 | 97 | test("DELETE should return correct route params", async (done) => { 98 | const res = await request.delete("/orders/1"); 99 | expect(res.status).toBe(200); 100 | expect(res.body).toEqual({ 101 | id: "1" 102 | }); 103 | done(); 104 | }); 105 | 106 | test("GET should return 401 on protected route if auth header is missing", async(done) => { 107 | const res = await request.get("/protected"); 108 | expect(res.status).toBe(401); 109 | expect(res.text).toBe("Not allowed"); 110 | done(); 111 | }); 112 | 113 | test("GET should return 200 on protected route if auth header is present", async (done) => { 114 | const res = await request 115 | .get("/protected") 116 | .set("authorization", "abc123"); 117 | expect(res.status).toBe(200); 118 | expect(res.text).toBe("protected route"); 119 | done(); 120 | }); 121 | 122 | test("GET no match", async(done) => { 123 | const res = await request.get("/nomatch"); 124 | expect(res.status).toBe(404); 125 | expect(res.text).toBe("Not found"); 126 | done(); 127 | }); 128 | }); -------------------------------------------------------------------------------- /src/__tests__/query-params.js: -------------------------------------------------------------------------------- 1 | const parse = require("../query-params"); 2 | describe("query params", () => { 3 | test("should parse query params", () => { 4 | expect(parse("/products?page=1&pageSize=100")).toEqual({ 5 | page: "1", 6 | pageSize: "100" 7 | }); 8 | }); 9 | 10 | test("should parse query params, one param", () => { 11 | expect(parse("/products?page=1")).toEqual({ 12 | page: "1" 13 | }); 14 | }); 15 | 16 | test("should parse query params, one param, string", () => { 17 | expect(parse("/products?page=abc")).toEqual({ 18 | page: "abc", 19 | }); 20 | }); 21 | 22 | test("should not crash when there are no params", () => { 23 | expect(parse("/products/114")).toEqual({}); 24 | }); 25 | }); -------------------------------------------------------------------------------- /src/__tests__/url-to-regex.js: -------------------------------------------------------------------------------- 1 | const parse = require("../url-to-regex"); 2 | 3 | describe("testing url", () => { 4 | test("should become correct Regex", () => { 5 | expect(parse("/products")).toBe("/products"); 6 | }); 7 | 8 | test("should give match with param", () => { 9 | expect(parse("/products/:id")).toBe("/products/(?\\w+)"); 10 | const m = "/products/114".match(new RegExp("/products/(?\\w+)")); 11 | 12 | expect(m.groups.id).toBe("114"); 13 | }); 14 | 15 | test("should give match with param v2", () => { 16 | expect(parse("/orders/:id/items/:item/")).toBe("/orders/(?\\w+)/items/(?\\w+)/"); 17 | }); 18 | 19 | test("regex", () => { 20 | expect(/\w/.test("a")).toBe(true); 21 | expect(new RegExp("/products").test("/products")).toBe(true); 22 | expect(/\products/.test("/products")).toBe(true); 23 | }); 24 | }); -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | const http = require("http"); 2 | const parse = require("./url-to-regex"); 3 | const queryParse = require("./query-params"); 4 | let server; 5 | 6 | function createResponse(res) { 7 | res.send = (message) => res.end(message); 8 | res.json = (data) => { 9 | res.setHeader("Content-Type", "application/json"); 10 | res.end(JSON.stringify(data)); 11 | }; 12 | return res; 13 | } 14 | 15 | function processMiddleware(middleware, req, res) { 16 | if (!middleware) { 17 | // resolve false 18 | return new Promise((resolve) => resolve(true)); 19 | } 20 | 21 | return new Promise((resolve) => { 22 | middleware(req, res, function () { 23 | resolve(true); 24 | }); 25 | }); 26 | } 27 | 28 | function pico() { 29 | let routeTable = {}; 30 | let parseMethod = "json"; // json, plain text 31 | 32 | function readBody(req) { 33 | return new Promise((resolve, reject) => { 34 | let body = ""; 35 | req.on("data", (chunk) => { 36 | body += "" + chunk; 37 | }); 38 | req.on("end", () => { 39 | resolve(body); 40 | }); 41 | req.on("error", (err) => { 42 | reject(err); 43 | }); 44 | }); 45 | } 46 | 47 | server = http.createServer(async(req, res) => { 48 | const routes = Object.keys(routeTable); 49 | let match = false; 50 | for(var i =0; i < routes.length; i++) { 51 | const route = routes[i]; 52 | const parsedRoute = parse(route); 53 | if ( 54 | new RegExp(parsedRoute).test(req.url) && 55 | routeTable[route][req.method.toLowerCase()] 56 | ) { 57 | let cb = routeTable[route][req.method.toLowerCase()]; 58 | let middleware = routeTable[route][`${req.method.toLowerCase()}-middleware`]; 59 | // console.log("regex", parsedRoute); 60 | const m = req.url.match(new RegExp(parsedRoute)); 61 | // console.log("params", m.groups); 62 | 63 | req.params = m.groups; 64 | req.query = queryParse(req.url); 65 | 66 | let body = await readBody(req); 67 | if (parseMethod === "json") { 68 | body = body ? JSON.parse(body) : {}; 69 | } 70 | req.body = body; 71 | 72 | const result = await processMiddleware(middleware, req, createResponse(res)); 73 | if (result) { 74 | cb(req, res); 75 | } 76 | 77 | match = true; 78 | break; 79 | } 80 | } 81 | if (!match) { 82 | res.statusCode = 404; 83 | res.end("Not found"); 84 | } 85 | }); 86 | 87 | function registerPath(path, cb, method, middleware) { 88 | if (!routeTable[path]) { 89 | routeTable[path] = {}; 90 | } 91 | routeTable[path] = { ...routeTable[path], [method]: cb, [method + "-middleware"]: middleware }; 92 | } 93 | 94 | return { 95 | get: (path, ...rest) => { 96 | if (rest.length === 1) { 97 | registerPath(path, rest[0] , "get"); 98 | } else { 99 | registerPath(path, rest[1], "get", rest[0]); 100 | } 101 | }, 102 | post: (path, ...rest) => { 103 | if (rest.length === 1) { 104 | registerPath(path, rest[0], "post"); 105 | } else { 106 | registerPath(path, rest[1], "post", rest[0]); 107 | } 108 | }, 109 | put: (path, ...rest) => { 110 | if (rest.length === 1) { 111 | registerPath(path, rest[0], "put"); 112 | } else { 113 | registerPath(path, rest[1], "put", rest[0]); 114 | } 115 | }, 116 | delete: (path, ...rest) => { 117 | if (rest.length === 1) { 118 | registerPath(path, rest[0], "delete"); 119 | } else { 120 | registerPath(path, rest[1], "delete", rest[0]); 121 | } 122 | }, 123 | bodyParse: (method) => parseMethod = method, 124 | listen: (port, cb) => { 125 | server.listen(port, cb); 126 | }, 127 | _server: server 128 | }; 129 | } 130 | 131 | module.exports = pico; -------------------------------------------------------------------------------- /src/query-params.js: -------------------------------------------------------------------------------- 1 | function parse(url) { 2 | const results = url.match(/\?(?.*)/); 3 | if (!results) { 4 | return {}; 5 | } 6 | const { groups: { query } } = results; 7 | 8 | const pairs = query.match(/(?\w+)=(?\w+)/g); 9 | const params = pairs.reduce((acc, curr) => { 10 | const [key, value] = curr.split(("=")); 11 | acc[key] = value; 12 | return acc; 13 | }, {}); 14 | return params; 15 | } 16 | 17 | module.exports = parse; -------------------------------------------------------------------------------- /src/url-to-regex.js: -------------------------------------------------------------------------------- 1 | function parse(url) { 2 | let str = ""; 3 | 4 | for (var i =0; i < url.length; i++) { 5 | const c = url.charAt(i); 6 | if (c === ":") { 7 | // eat all characters 8 | let param = ""; 9 | for (var j = i + 1; j < url.length; j++) { 10 | if (/\w/.test(url.charAt(j))) { 11 | param += url.charAt(j); 12 | } else { 13 | break; 14 | } 15 | } 16 | str += `(?<${param}>\\w+)`; 17 | i = j -1; 18 | } else { 19 | str += c; 20 | } 21 | } 22 | return str; 23 | } 24 | 25 | module.exports = parse; --------------------------------------------------------------------------------