├── .github ├── API │ ├── api.md │ ├── application.md │ ├── middlewares.md │ ├── opine.md │ ├── request.md │ ├── response.md │ └── router.md ├── CHANGELOG.md ├── CODEOWNERS.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── icon.jpg ├── icon.png ├── icon.svg └── workflows │ ├── benchmark.yml │ ├── pr_benchmark_updater.yml │ ├── publish-docs.yml │ ├── publish-egg.yml │ └── test.yml ├── .gitignore ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── EXPRESS_LICENSE.md ├── LICENSE.md ├── Makefile ├── README.md ├── SECURITY.md ├── benchmarks ├── middleware.ts └── run.sh ├── deps.ts ├── docs ├── _config.yaml ├── assets │ ├── css │ │ └── main.css │ ├── images │ │ ├── icons.png │ │ ├── icons@2x.png │ │ ├── widgets.png │ │ └── widgets@2x.png │ └── js │ │ ├── main.js │ │ └── search.json ├── classes │ ├── _request_.wrappedrequest.html │ ├── _response_.response.html │ ├── _utils_createerror_.httperror.html │ ├── _utils_createerror_.httperrorimpl.html │ └── _view_.view.html ├── globals.html ├── index.html ├── interfaces │ ├── _types_.__global.opine.application.html │ ├── _types_.__global.opine.request.html │ ├── _types_.__global.opine.response.html │ ├── _types_.application.html │ ├── _types_.byterange.html │ ├── _types_.cookie.html │ ├── _types_.dictionary.html │ ├── _types_.handler.html │ ├── _types_.iroute.html │ ├── _types_.irouter.html │ ├── _types_.irouterhandler.html │ ├── _types_.iroutermatcher.html │ ├── _types_.mediatype.html │ ├── _types_.nextfunction.html │ ├── _types_.opine.html │ ├── _types_.opinerequest.html │ ├── _types_.opineresponse.html │ ├── _types_.paramsdictionary.html │ ├── _types_.rangeparseroptions.html │ ├── _types_.rangeparserrange.html │ ├── _types_.rangeparserranges.html │ ├── _types_.requesthandler.html │ ├── _types_.requestranges.html │ ├── _types_.router.html │ ├── _types_.routerconstructor.html │ ├── _types_.routeroptions.html │ ├── _utils_createerror_.ierror.html │ └── _utils_createerror_.props.html └── modules │ ├── _application_.html │ ├── _methods_.html │ ├── _middleware_bodyparser_getcharset_.html │ ├── _middleware_bodyparser_json_.html │ ├── _middleware_bodyparser_raw_.html │ ├── _middleware_bodyparser_read_.html │ ├── _middleware_bodyparser_text_.html │ ├── _middleware_bodyparser_typechecker_.html │ ├── _middleware_bodyparser_urlencoded_.html │ ├── _middleware_init_.html │ ├── _middleware_query_.html │ ├── _middleware_servestatic_.html │ ├── _opine_.html │ ├── _request_.html │ ├── _response_.html │ ├── _router_index_.html │ ├── _router_layer_.html │ ├── _router_route_.html │ ├── _types_.__global.html │ ├── _types_.__global.opine.html │ ├── _types_.html │ ├── _utils_compileetag_.html │ ├── _utils_compilequeryparser_.html │ ├── _utils_compiletrust_.html │ ├── _utils_contentdisposition_.html │ ├── _utils_cookies_.html │ ├── _utils_createerror_.html │ ├── _utils_definegetter_.html │ ├── _utils_etag_.html │ ├── _utils_finalhandler_.html │ ├── _utils_forwarded_.html │ ├── _utils_fresh_.html │ ├── _utils_merge_.html │ ├── _utils_mergedescriptors_.html │ ├── _utils_normalizetype_.html │ ├── _utils_parseurl_.html │ ├── _utils_pathtoregex_.html │ ├── _utils_proxyaddr_.html │ ├── _utils_send_.html │ ├── _utils_stringify_.html │ └── _view_.html ├── egg.json ├── examples ├── README.md ├── assigned-port │ ├── README.md │ ├── index.test.ts │ └── index.ts ├── content-negotiation │ ├── README.md │ ├── db.ts │ ├── index.test.ts │ ├── index.ts │ └── users.ts ├── cors │ ├── README.md │ ├── index.test.ts │ └── index.ts ├── dejs │ ├── README.md │ ├── index.test.ts │ ├── index.ts │ ├── public │ │ └── stylesheets │ │ │ └── style.css │ └── views │ │ ├── footer.html │ │ ├── header.html │ │ └── users.html ├── downloads │ ├── README.md │ ├── files │ │ ├── CCTV大赛上海分赛区.txt │ │ └── amazing.txt │ ├── index.test.ts │ └── index.ts ├── error-pages │ ├── README.md │ ├── index.test.ts │ ├── index.ts │ ├── public │ │ └── stylesheets │ │ │ └── style.css │ └── views │ │ ├── 404.ejs │ │ ├── 500.ejs │ │ ├── error_header.ejs │ │ ├── footer.ejs │ │ └── index.ejs ├── error │ ├── README.md │ ├── index.test.ts │ └── index.ts ├── eta │ ├── README.md │ ├── index.test.ts │ ├── index.ts │ ├── public │ │ └── stylesheets │ │ │ └── style.css │ └── views │ │ ├── footer.html │ │ ├── header.html │ │ ├── index.html │ │ ├── layout.html │ │ └── users.html ├── graphql │ ├── README.md │ ├── index.test.ts │ └── index.ts ├── hello-world │ ├── README.md │ ├── index.test.ts │ └── index.ts ├── json │ ├── README.md │ ├── index.test.ts │ └── index.ts ├── location │ ├── README.md │ ├── index.test.ts │ └── index.ts ├── multi-router │ ├── README.md │ ├── controllers │ │ ├── APIv1.ts │ │ └── APIv2.ts │ ├── index.test.ts │ └── index.ts ├── mvc │ ├── README.md │ ├── controllers │ │ ├── main │ │ │ └── index.ts │ │ ├── pet │ │ │ ├── index.ts │ │ │ └── views │ │ │ │ ├── edit.ejs │ │ │ │ └── show.ejs │ │ ├── user-pet │ │ │ └── index.ts │ │ └── user │ │ │ ├── index.ts │ │ │ └── views │ │ │ ├── edit.ejs │ │ │ ├── list.ejs │ │ │ └── show.ejs │ ├── db.ts │ ├── deps.ts │ ├── index.test.ts │ ├── index.ts │ ├── lib │ │ └── boot.ts │ ├── public │ │ └── style.css │ └── views │ │ ├── 404.ejs │ │ └── 5xx.ejs ├── proxy │ ├── README.md │ ├── index.test.ts │ └── index.ts ├── raw │ ├── README.md │ ├── index.test.ts │ └── index.ts ├── react │ ├── README.md │ ├── client.tsx │ ├── components │ │ ├── App.tsx │ │ ├── List.tsx │ │ └── Title.tsx │ ├── public │ │ └── stylesheets │ │ │ └── style.css │ ├── server.tsx │ ├── services │ │ ├── fetchDoggos.ts │ │ └── wrapPromise.ts │ └── views │ │ ├── footer.html │ │ ├── header.html │ │ └── main.html ├── redirect │ ├── README.md │ ├── index.test.ts │ └── index.ts ├── static-files │ ├── README.md │ ├── index.test.ts │ ├── index.ts │ └── public │ │ ├── css │ │ └── style.css │ │ ├── hello.txt │ │ └── js │ │ ├── app.js │ │ └── helper.js ├── text │ ├── README.md │ ├── index.test.ts │ └── index.ts ├── urlencoded │ ├── README.md │ ├── index.test.ts │ └── index.ts └── websockets │ ├── client.ts │ └── server.ts ├── mod.ts ├── src ├── application.ts ├── methods.ts ├── middleware │ ├── bodyParser │ │ ├── getCharset.ts │ │ ├── json.ts │ │ ├── raw.ts │ │ ├── read.ts │ │ ├── text.ts │ │ ├── typeChecker.ts │ │ └── urlencoded.ts │ ├── init.ts │ ├── query.ts │ └── serveStatic.ts ├── opine.ts ├── request.ts ├── response.ts ├── router │ ├── index.ts │ ├── layer.ts │ └── route.ts ├── types.ts ├── utils │ ├── compileETag.ts │ ├── compileQueryParser.ts │ ├── compileTrust.ts │ ├── contentDisposition.ts │ ├── cookies.ts │ ├── createError.ts │ ├── defineGetter.ts │ ├── etag.ts │ ├── finalHandler.ts │ ├── forwarded.ts │ ├── fresh.ts │ ├── merge.ts │ ├── mergeDescriptors.ts │ ├── normalizeType.ts │ ├── parseUrl.ts │ ├── pathToRegex.ts │ ├── proxyAddr.ts │ ├── send.ts │ └── stringify.ts └── view.ts ├── test ├── deps.ts ├── fixtures │ ├── % of dogs.txt │ ├── .hidden │ ├── .hidden.txt │ ├── .mine │ │ └── name.txt │ ├── .name │ ├── blog │ │ ├── index.html │ │ └── post │ │ │ └── index.tmpl │ ├── default_layout │ │ ├── name.tmpl │ │ └── user.tmpl │ ├── deno.html │ ├── dinos │ │ └── names.txt │ ├── do..ts.txt │ ├── email.tmpl │ ├── empty.txt │ ├── foo bar │ ├── local_layout │ │ └── user.tmpl │ ├── name.html │ ├── name.tmpl │ ├── name.txt │ ├── nums.txt │ ├── pets │ │ └── index.html │ ├── some thing.txt │ ├── todo.html │ ├── todo.txt │ ├── user.html │ ├── user.tmpl │ └── users │ │ ├── deno.txt │ │ └── index.html ├── support │ └── tmpl.ts ├── units │ ├── Route.test.ts │ ├── Router.test.ts │ ├── app.all.test.ts │ ├── app.delete.test.ts │ ├── app.engine.test.ts │ ├── app.head.test.ts │ ├── app.listen.test.ts │ ├── app.locals.test.ts │ ├── app.options.test.ts │ ├── app.param.test.ts │ ├── app.render.test.ts │ ├── app.request.test.ts │ ├── app.response.test.ts │ ├── app.route.test.ts │ ├── app.router.test.ts │ ├── app.routes.error.test.ts │ ├── app.test.ts │ ├── app.use.test.ts │ ├── bodyParser.json.test.ts │ ├── bodyParser.raw.test.ts │ ├── bodyParser.text.test.ts │ ├── bodyParser.urlencoded.test.ts │ ├── config.test.ts │ ├── etag.test.ts │ ├── exports.test.ts │ ├── middleware.basic.test.ts │ ├── regression.test.ts │ ├── req.accepts.test.ts │ ├── req.acceptsCharsets.test.ts │ ├── req.acceptsEncoding.test.ts │ ├── req.acceptsLanguages.test.ts │ ├── req.baseUrl.test.ts │ ├── req.fresh.test.ts │ ├── req.get.test.ts │ ├── req.hostname.test.ts │ ├── req.ip.test.ts │ ├── req.ips.test.ts │ ├── req.is.test.ts │ ├── req.path.test.ts │ ├── req.protocol.test.ts │ ├── req.query.test.ts │ ├── req.range.test.ts │ ├── req.route.test.ts │ ├── req.secure.test.ts │ ├── req.stale.test.ts │ ├── req.subdomains.test.ts │ ├── req.upgrade.test.ts │ ├── req.xhr.test.ts │ ├── res.append.test.ts │ ├── res.attachment.test.ts │ ├── res.clearCookie.test.ts │ ├── res.cookie.test.ts │ ├── res.download.test.ts │ ├── res.format.test.ts │ ├── res.get.test.ts │ ├── res.json.test.ts │ ├── res.jsonp.test.ts │ ├── res.links.test.ts │ ├── res.locals.test.ts │ ├── res.location.test.ts │ ├── res.redirect.test.ts │ ├── res.removeHeader.test.ts │ ├── res.render.test.ts │ ├── res.send.test.ts │ ├── res.sendFile.test.ts │ ├── res.set.test.ts │ ├── res.setHeader.test.ts │ ├── res.setStatus.test.ts │ ├── res.type.test.ts │ ├── res.vary.test.ts │ ├── send.test.ts │ └── serveStatic.test.ts └── utils.ts └── version.ts /.github/API/api.md: -------------------------------------------------------------------------------- 1 | # 2.x API 2 | 3 | Adapted from the [ExpressJS API Docs](https://expressjs.com/en/4x/api.html). 4 | 5 | ## Contents 6 | 7 | - [opine()](./opine.md) 8 | - [Application](./application.md) 9 | - [Request](./request.md) 10 | - [Response](./response.md) 11 | - [Router](./router.md) 12 | - [Middlewares](./middlewares.md) 13 | -------------------------------------------------------------------------------- /.github/API/opine.md: -------------------------------------------------------------------------------- 1 | # 2.x API 2 | 3 | Adapted from the [ExpressJS API Docs](https://expressjs.com/en/4x/api.html). 4 | 5 | ## opine() 6 | 7 | Creates an Opine application. The `opine()` function is a top-level function 8 | exported by the Opine module: 9 | 10 | ```ts 11 | import opine from "https://deno.land/x/opine@2.3.4/mod.ts"; 12 | 13 | const app = opine(); 14 | ``` 15 | 16 | The `opine()` function is also exported as a named export: 17 | 18 | ```ts 19 | import { opine } from "https://deno.land/x/opine@2.3.4/mod.ts"; 20 | 21 | const app = opine(); 22 | ``` 23 | -------------------------------------------------------------------------------- /.github/CODEOWNERS.md: -------------------------------------------------------------------------------- 1 | * @cmorten 2 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this repository 2 | 3 | First of all, thanks for taking the time to read this document and contributing 4 | to our codebase! :tada: :beers: 5 | 6 | ## Getting started 7 | 8 | If you're working on an existing issue then awesome! Let us know by dropping a 9 | comment in the issue. 10 | 11 | If it's a new bug fix or feature that you would like to contribute, then please 12 | raise an issue so it can be tracked ( and to help out others who are 13 | experiencing the same issue / want the new thing know that it's being looked at! 14 | ). Be sure to check for existing issues before raising your own! 15 | 16 | ## Working on your feature 17 | 18 | ### Branching 19 | 20 | On this project we follow mainline development (or trunk based development), and 21 | our default branch is `main`. 22 | 23 | Therefore you need to branch from `main` and merge into `main`. 24 | 25 | ### Coding style 26 | 27 | Generally try to match the style and conventions of the code around your 28 | changes. Ultimately we want code that is clear, concise, consistent and easy to 29 | read. 30 | 31 | As this is a Deno project will also insist on meeting the Deno `fmt` standards. 32 | 33 | ### Format Code 34 | 35 | To format the code run: 36 | 37 | ```bash 38 | make fmt 39 | ``` 40 | 41 | ### Tests 42 | 43 | Before opening a PR, please run the following command to make sure your branch 44 | will build and pass all the checks and tests: 45 | 46 | ```console 47 | make ci 48 | ``` 49 | 50 | ## Opening a PR 51 | 52 | Once you're confident your branch is ready to review, open a PR against `main` 53 | on this repo. 54 | 55 | Please use the PR template as a guide, but if your change doesn't quite fit it, 56 | feel free to customize. 57 | 58 | ## Merging and publishing 59 | 60 | When your feature branch / PR has been tested and has an approval, it is then 61 | ready to merge. Please contact the maintainer to action the merge. 62 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [cmorten] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue 2 | 3 | Setup: 4 | 5 | - Deno Version: 6 | - v8 Version: 7 | - Typescript Version: 8 | - Opine Version: 9 | 10 | ## Details 11 | 12 | > Please replace this quote block with the details of the feature / bug you wish 13 | > to be addressed. If it is a bug please do your best to add steps to reproduce. 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue 2 | 3 | Fixes #. 4 | 5 | ## Details 6 | 7 | Brief summary of PR purpose and code changes. 8 | 9 | ## CheckList 10 | 11 | - [ ] PR starts with [#_ISSUE_ID_]. 12 | - [ ] Has been tested (where required). 13 | -------------------------------------------------------------------------------- /.github/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/opine/23a24f4334c65a1d6042c2c59a627f3f2548c026/.github/icon.jpg -------------------------------------------------------------------------------- /.github/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/opine/23a24f4334c65a1d6042c2c59a627f3f2548c026/.github/icon.png -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Run benchmark 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | benchmark: 8 | runs-on: macos-latest 9 | steps: 10 | - run: HOMEBREW_NO_AUTO_UPDATE=1 brew install wrk 11 | - uses: actions/checkout@v2 12 | - uses: denolib/setup-deno@v2 13 | with: 14 | deno-version: 1.32.4 15 | - run: | 16 | mkdir -p artifacts 17 | cat > artifacts/message.md < 21 | PR to merge $GITHUB_HEAD_REF $GITHUB_SHA -> $GITHUB_BASE_REF 22 | 23 | \`\`\`console 24 | $(make benchmark) 25 | \`\`\` 26 | 27 | 28 | EOF 29 | - uses: actions/upload-artifact@v1 30 | with: 31 | name: pr_message 32 | path: artifacts 33 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish TypeDocs 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | publish-docs: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: 12 16 | - run: make deps 17 | - uses: denolib/setup-deno@v2 18 | with: 19 | deno-version: 1.32.4 20 | - run: make typedoc 21 | - run: make ci 22 | - uses: stefanzweifel/git-auto-commit-action@v4 23 | with: 24 | commit_message: publish typedocs 25 | push_options: --force 26 | -------------------------------------------------------------------------------- /.github/workflows/publish-egg.yml: -------------------------------------------------------------------------------- 1 | name: Publish Egg 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | publish-egg: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: denolib/setup-deno@v2 13 | with: 14 | deno-version: 1.32.4 15 | - run: deno install -A -f --unstable -n eggs https://x.nest.land/eggs@0.3.10/eggs.ts 16 | - run: | 17 | export PATH="/home/runner/.deno/bin:$PATH" 18 | eggs link ${NEST_LAND_KEY} 19 | eggs publish --yes 20 | env: 21 | NEST_LAND_KEY: ${{secrets.NEST_LAND_KEY}} 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-latest] 14 | deno-version: [1.32.4] 15 | 16 | runs-on: ${{ matrix.os }} 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: denolib/setup-deno@v2 21 | with: 22 | deno-version: ${{ matrix.deno-version }} 23 | - run: make ci 24 | test-win: 25 | strategy: 26 | matrix: 27 | os: [windows-latest] 28 | deno-version: [1.32.4] 29 | 30 | runs-on: ${{ matrix.os }} 31 | 32 | steps: 33 | - uses: actions/checkout@v2 34 | - uses: denolib/setup-deno@v2 35 | with: 36 | deno-version: ${{ matrix.deno-version }} 37 | - run: make build 38 | - run: make test 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.unstable": true, 4 | "[typescript]": { 5 | "editor.defaultFormatter": "denoland.vscode-deno" 6 | }, 7 | "[typescriptreact]": { 8 | "editor.defaultFormatter": "denoland.vscode-deno" 9 | }, 10 | "editor.defaultFormatter": "denoland.vscode-deno", 11 | "deno.suggest.imports.hosts": { 12 | "https://deno.land": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /EXPRESS_LICENSE.md: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2009-2014 TJ Holowaychuk 4 | 5 | Copyright (c) 2013-2014 Roman Shtylman 6 | 7 | Copyright (c) 2014-2015 Douglas Christopher Wilson 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | this software and associated documentation files (the 'Software'), to deal in 11 | the Software without restriction, including without limitation the rights to 12 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 13 | the Software, and to permit persons to whom the Software is furnished to do so, 14 | subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 21 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 22 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 23 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 24 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2020 Craig Morten 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: benchmark build ci deps doc fmt fmt-check lint test typedoc 2 | 3 | FILES_TO_FORMAT = ./src ./benchmarks/ ./test ./examples deps.ts mod.ts version.ts 4 | 5 | benchmark: 6 | @echo "opine: 1 middleware" 7 | @echo "================================" 8 | @./benchmarks/run.sh 1 ./benchmarks/middleware.ts 9 | @echo "" 10 | @echo "opine: 10 middleware" 11 | @echo "================================" 12 | @./benchmarks/run.sh 10 ./benchmarks/middleware.ts 13 | @echo "" 14 | @echo "opine: 50 middleware" 15 | @echo "================================" 16 | @./benchmarks/run.sh 50 ./benchmarks/middleware.ts 17 | @echo "" 18 | @echo "std/http benchmark" 19 | @echo "================================" 20 | @./benchmarks/run.sh 0 https://deno.land/std/http/bench.ts 21 | @echo "" 22 | @echo "deno_http_native benchmark" 23 | @echo "================================" 24 | @./benchmarks/run.sh 0 https://raw.githubusercontent.com/denoland/deno/main/cli/bench/deno_http_native.js 25 | @echo "" 26 | 27 | build: 28 | @deno run --allow-net="deno.land" --allow-env --reload mod.ts 29 | 30 | ci: 31 | @make fmt-check 32 | @make build 33 | @make test 34 | 35 | deps: 36 | @npm install -g typescript@4.9.5 typedoc@0.19.2 37 | 38 | doc: 39 | @deno doc ./mod.ts 40 | 41 | fmt: 42 | @deno fmt $(FILES_TO_FORMAT) 43 | 44 | fmt-check: 45 | @deno fmt --check $(FILES_TO_FORMAT) 46 | 47 | lint: 48 | @deno lint --unstable $(FILES_TO_FORMAT) 49 | 50 | test: 51 | @deno test --allow-net --allow-read ./test/units/ 52 | @deno test --allow-net --allow-read --allow-env --unstable ./examples/ 53 | 54 | typedoc: 55 | @rm -rf docs 56 | @typedoc --ignoreCompilerErrors --out ./docs --mode modules --includeDeclarations --excludeExternals --name opine ./src 57 | @make fmt 58 | @make fmt 59 | @echo 'future: true\nencoding: "UTF-8"\ninclude:\n - "_*_.html"\n - "_*_.*.html"' > ./docs/_config.yaml 60 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policies and Procedures 2 | 3 | This document outlines security procedures and general policies for the Opine 4 | project. 5 | 6 | - [Reporting a Bug](#reporting-a-bug) 7 | - [Disclosure Policy](#disclosure-policy) 8 | - [Comments on this Policy](#comments-on-this-policy) 9 | 10 | ## Reporting a Bug 11 | 12 | The Opine team and community take all security bugs in Opine seriously. Thank 13 | you for improving the security of Opine. We appreciate your efforts and 14 | responsible disclosure and will make every effort to acknowledge your 15 | contributions. 16 | 17 | Report security bugs by emailing the lead maintainer in the 18 | [CODEOWNERS](./.github/CODEOWNERS.md) file. 19 | 20 | The lead maintainer will acknowledge your email, and will send a response 21 | indicating the next steps in handling your report. After the initial reply to 22 | your report, the maintainer team will endeavor to keep you informed of the 23 | progress towards a fix and full announcement, and may ask for additional 24 | information or guidance. 25 | 26 | Report security bugs in third-party modules to the person or team maintaining 27 | the module. 28 | 29 | ## Disclosure Policy 30 | 31 | When the maintainer team receives a security bug report, they will assign it to 32 | a primary handler. This person will coordinate the fix and release process, 33 | involving the following steps: 34 | 35 | - Confirm the problem and determine the affected versions. 36 | - Audit code to find any potential similar problems. 37 | - Prepare fixes for all releases still under maintenance. These fixes will be 38 | released as fast as possible to GitHub. 39 | 40 | ## Comments on this Policy 41 | 42 | If you have suggestions on how this process could be improved please submit a 43 | pull request. 44 | -------------------------------------------------------------------------------- /benchmarks/middleware.ts: -------------------------------------------------------------------------------- 1 | import opine from "../mod.ts"; 2 | import { 3 | Application, 4 | NextFunction, 5 | OpineRequest, 6 | OpineResponse, 7 | } from "../src/types.ts"; 8 | 9 | const app: Application = opine(); 10 | 11 | let middlewareCount: number = parseInt(Deno.env.get("MW") || "1"); 12 | 13 | console.log("%s middleware", middlewareCount); 14 | 15 | while (middlewareCount--) { 16 | app.use( 17 | (_req: OpineRequest, _res: OpineResponse, next: NextFunction): void => { 18 | next(); 19 | }, 20 | ); 21 | } 22 | 23 | app.use((_req: OpineRequest, res: OpineResponse, _next: NextFunction): void => { 24 | res.send("Hello World"); 25 | }); 26 | 27 | app.listen({ port: 3333 }); 28 | -------------------------------------------------------------------------------- /benchmarks/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo 3 | MW=$1 deno run --allow-env --allow-net --allow-read --unstable $2 'localhost:3333' & 4 | pid=$! 5 | 6 | while [[ "$(curl -s -o /dev/null -I -w '%{http_code}' 'http://localhost:3333/')" != "200" ]]; do 7 | sleep 5; 8 | done 9 | 10 | wrk 'http://localhost:3333/?foo[bar]=baz' -d '3s' --latency 11 | 12 | kill $pid 13 | -------------------------------------------------------------------------------- /deps.ts: -------------------------------------------------------------------------------- 1 | export { 2 | serve, 3 | Server, 4 | serveTls, 5 | } from "https://deno.land/std@0.183.0/http/server.ts"; 6 | export type { ConnInfo } from "https://deno.land/std@0.183.0/http/server.ts"; 7 | export { 8 | Status, 9 | STATUS_TEXT, 10 | } from "https://deno.land/std@0.183.0/http/http_status.ts"; 11 | export { 12 | deleteCookie, 13 | setCookie, 14 | } from "https://deno.land/std@0.183.0/http/cookie.ts"; 15 | export type { Cookie } from "https://deno.land/std@0.183.0/http/cookie.ts"; 16 | export { 17 | basename, 18 | dirname, 19 | extname, 20 | fromFileUrl, 21 | isAbsolute, 22 | join, 23 | normalize, 24 | resolve, 25 | sep, 26 | } from "https://deno.land/std@0.183.0/path/mod.ts"; 27 | export { setImmediate } from "https://deno.land/std@0.177.0/node/timers.ts"; 28 | export { parse } from "https://deno.land/std@0.177.0/node/querystring.ts"; 29 | export { default as EventEmitter } from "https://deno.land/std@0.177.0/node/events.ts"; 30 | export { Sha1 } from "https://deno.land/std@0.160.0/hash/sha1.ts"; 31 | export { 32 | readableStreamFromReader, 33 | readAll, 34 | readerFromStreamReader, 35 | } from "https://deno.land/std@0.172.0/streams/conversion.ts"; 36 | 37 | export { 38 | charset, 39 | contentType, 40 | lookup, 41 | } from "https://deno.land/x/media_types@v3.0.3/mod.ts"; 42 | export { Accepts } from "https://deno.land/x/accepts@2.1.1/mod.ts"; 43 | export { 44 | hasBody, 45 | typeofrequest, 46 | } from "https://deno.land/x/type_is@1.0.3/mod.ts"; 47 | export { isIP } from "https://deno.land/x/isIP@1.0.0/mod.ts"; 48 | export { vary } from "https://deno.land/x/vary@1.0.0/mod.ts"; 49 | export { escapeHtml } from "https://deno.land/x/escape_html@1.0.0/mod.ts"; 50 | export { encodeUrl } from "https://deno.land/x/encodeurl@1.0.0/mod.ts"; 51 | export { gunzip, inflate } from "https://deno.land/x/compress@v0.4.1/mod.ts"; 52 | 53 | export { default as parseRange } from "https://cdn.skypack.dev/range-parser@1.2.1?dts"; 54 | export { default as qs } from "https://cdn.skypack.dev/qs@6.10.3?dts"; 55 | export { default as ipaddr } from "https://cdn.skypack.dev/ipaddr.js@2.0.1?dts"; 56 | export { default as ms } from "https://cdn.skypack.dev/ms@2.1.3?dts"; 57 | -------------------------------------------------------------------------------- /docs/_config.yaml: -------------------------------------------------------------------------------- 1 | future: true 2 | encoding: "UTF-8" 3 | include: 4 | - "_*_.html" 5 | - "_*_.*.html" 6 | -------------------------------------------------------------------------------- /docs/assets/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/opine/23a24f4334c65a1d6042c2c59a627f3f2548c026/docs/assets/images/icons.png -------------------------------------------------------------------------------- /docs/assets/images/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/opine/23a24f4334c65a1d6042c2c59a627f3f2548c026/docs/assets/images/icons@2x.png -------------------------------------------------------------------------------- /docs/assets/images/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/opine/23a24f4334c65a1d6042c2c59a627f3f2548c026/docs/assets/images/widgets.png -------------------------------------------------------------------------------- /docs/assets/images/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/opine/23a24f4334c65a1d6042c2c59a627f3f2548c026/docs/assets/images/widgets@2x.png -------------------------------------------------------------------------------- /egg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opine", 3 | "description": "Minimalist web framework for Deno ported from ExpressJS.", 4 | "version": "2.3.4", 5 | "repository": "https://github.com/cmorten/opine", 6 | "stable": true, 7 | "checkFormat": false, 8 | "checkTests": false, 9 | "checkInstallation": false, 10 | "check": false, 11 | "files": [ 12 | "./mod.ts", 13 | "./deps.ts", 14 | "./version.ts", 15 | "./src/**/*", 16 | "./README.md", 17 | "./LICENSE.md", 18 | "./EXPRESS_LICENSE.md", 19 | "./.github/CHANGELOG.md" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This directory contains a series of self-contained examples that you can use as 4 | starting points for your app, or as snippets to pull into your existing 5 | applications: 6 | 7 | - [assigned-port](./assigned-port) - A basic example of starting an Opine server 8 | with a port that is assigned for you. 9 | - [content-negotiation](./content-negotiation) - An example of how to perform 10 | content negotiation using the `res.format()` method. 11 | - [cors](./cors) - An example of using CORS with Opine. 12 | - [dejs](./dejs) - Example of how to use Opine's template engine and rendering 13 | capabilities with the `dejs` module. 14 | - [downloads](./downloads) - Dummy file index server using path matching 15 | patterns and `res.download()` to serve files to the user. 16 | - [error](./error) - An example of how to use and write error middleware. 17 | - [error-pages](./error-pages) - Example of how to use Opine's template engine 18 | and rendering capabilities for custom error pages. 19 | - [eta](./eta) - Example of how to use Opine's template engine and rendering 20 | capabilities with the `eta` template engine. 21 | - [graphql](./graphql) - Example of how to use Opine with 22 | [gql](https://github.com/deno-libs/gql) for a simple GraphQL server. 23 | - [hello-world](./hello-world) - A basic example of how to configure and start 24 | an Opine server. 25 | - [json](./json) - An example of how to use the `json` body-parser middleware in 26 | your Opine applications. 27 | - [location](./location) - An example of how set the `Location` header using 28 | `res.location()` for a `301` redirect. 29 | - [multi-router](./multi-router) - An example of how to use the Opine `Router` 30 | to mount several controllers onto a path within an application / API. 31 | - [mvc](./mvc) - A basic MVC-style controllers example. 32 | - [proxy](./proxy) - Example using `opine-http-proxy` as a proxy middleware. 33 | - [raw](./raw) - An example of how to use the `raw` body-parser middleware in 34 | your Opine applications. 35 | - [react](./react) - An example of how you can use Opine with React. 36 | - [redirect](./redirect) - An example of how to redirect using `res.redirect`. 37 | - [static-files](./static-files) - An example of how to serve static files to a 38 | user using the Opine `serveStatic` middleware. 39 | - [text](./text) - An example of how to use the `text` body-parser middleware in 40 | your Opine applications. 41 | - [urlencoded](./urlencoded) - An example of how to use the `urlencoded` 42 | body-parser middleware in your Opine applications. 43 | -------------------------------------------------------------------------------- /examples/assigned-port/README.md: -------------------------------------------------------------------------------- 1 | # assigned-port 2 | 3 | A basic example of starting an Opine server with a port that is assigned for 4 | you. 5 | 6 | ## How to run this example 7 | 8 | Run this example using: 9 | 10 | ```bash 11 | deno run --allow-net --allow-read ./examples/assigned-port/index.ts 12 | ``` 13 | 14 | if have the repo cloned locally _OR_ 15 | 16 | ```bash 17 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/assigned-port/index.ts 18 | ``` 19 | 20 | if you don't! 21 | -------------------------------------------------------------------------------- /examples/assigned-port/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("assigned-port", () => { 6 | it("should respond with 'Hello Deno!' on the root path", (done) => { 7 | superdeno(app) 8 | .get("/") 9 | .expect(200, "Hello Deno!", done); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /examples/assigned-port/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/assigned-port/index.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/assigned-port/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import opine from "../../mod.ts"; 14 | 15 | const app = opine(); 16 | 17 | app.get("/", function (_req, res) { 18 | res.send("Hello Deno!"); 19 | }); 20 | 21 | if (import.meta.main) { 22 | // Providing no port results in an available port being assigned 23 | // for you. You can determine the port using the returned Server 24 | // object. 25 | const server = app.listen(); 26 | const address = server.addrs[0] as Deno.NetAddr; 27 | console.log(`Server started on ${address.hostname}:${address.port}`); 28 | } 29 | 30 | export { app }; 31 | -------------------------------------------------------------------------------- /examples/content-negotiation/README.md: -------------------------------------------------------------------------------- 1 | # content-negotiation 2 | 3 | An example of how to perform content negotiation using the `res.format()` method 4 | in an Opine server. 5 | 6 | ## How to run this example 7 | 8 | Run this example using: 9 | 10 | ```bash 11 | deno run --allow-net --allow-read ./examples/content-negotiation/index.ts 12 | ``` 13 | 14 | if have the repo cloned locally _OR_ 15 | 16 | ```bash 17 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/content-negotiation/index.ts 18 | ``` 19 | 20 | if you don't! 21 | 22 | Then try: 23 | 24 | 1. Opening http://localhost:3000/ in a browser - should see a HTML list. 25 | 2. `curl -X GET http://localhost:3000 -H 'Accept: text/plain'` - should see a 26 | plaintext list. 27 | 3. `curl -X GET http://localhost:3000 -H 'Accept: application/json'` - should 28 | see a JSON object. 29 | 30 | You can also try the above on the `/users` route. 31 | -------------------------------------------------------------------------------- /examples/content-negotiation/db.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { name: "Deno" }, 3 | { name: "Opine" }, 4 | { name: "Express" }, 5 | ]; 6 | -------------------------------------------------------------------------------- /examples/content-negotiation/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("content-negotiation", () => { 6 | describe("when accepting HTML", () => { 7 | it("should respond with HTML on the root path", (done) => { 8 | superdeno(app) 9 | .get("/") 10 | .accept("text/html") 11 | .expect("Content-Type", /text\/html/) 12 | .expect( 13 | 200, 14 | "
  • Deno
  • Opine
  • Express
", 15 | done, 16 | ); 17 | }); 18 | 19 | it("should respond with HTML on the users path", (done) => { 20 | superdeno(app) 21 | .get("/users") 22 | .accept("text/html") 23 | .expect("Content-Type", /text\/html/) 24 | .expect( 25 | 200, 26 | "
  • Deno
  • Opine
  • Express
", 27 | done, 28 | ); 29 | }); 30 | }); 31 | 32 | describe("when accepting text", () => { 33 | it("should respond with text on the root path", (done) => { 34 | superdeno(app) 35 | .get("/") 36 | .accept("text/plain") 37 | .expect("Content-Type", /text\/plain/) 38 | .expect( 39 | 200, 40 | " - Deno\n - Opine\n - Express\n", 41 | done, 42 | ); 43 | }); 44 | 45 | it("should respond with text on the users path", (done) => { 46 | superdeno(app) 47 | .get("/users") 48 | .accept("text/plain") 49 | .expect("Content-Type", /text\/plain/) 50 | .expect( 51 | 200, 52 | " - Deno\n - Opine\n - Express\n", 53 | done, 54 | ); 55 | }); 56 | }); 57 | 58 | describe("when accepting JSON", () => { 59 | it("should respond with JSON on the root path", (done) => { 60 | superdeno(app) 61 | .get("/") 62 | .accept("application/json") 63 | .expect("Content-Type", /application\/json/) 64 | .expect( 65 | 200, 66 | '[{"name":"Deno"},{"name":"Opine"},{"name":"Express"}]', 67 | done, 68 | ); 69 | }); 70 | 71 | it("should respond with JSON on the users path", (done) => { 72 | superdeno(app) 73 | .get("/users") 74 | .accept("application/json") 75 | .expect("Content-Type", /application\/json/) 76 | .expect( 77 | 200, 78 | '[{"name":"Deno"},{"name":"Opine"},{"name":"Express"}]', 79 | done, 80 | ); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /examples/content-negotiation/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/content-negotiation/index.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/content-negotiation/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import { opine } from "../../mod.ts"; 14 | import { users } from "./db.ts"; 15 | import type { OpineRequest, OpineResponse } from "../../src/types.ts"; 16 | 17 | const app = opine(); 18 | 19 | // So either you can deal with different types of formatting 20 | // for expected response in index.ts 21 | app.get("/", (_req, res) => { 22 | res.format({ 23 | html() { 24 | res.send( 25 | `
    ${users.map((user) => `
  • ${user.name}
  • `).join("")}
`, 26 | ); 27 | }, 28 | 29 | text() { 30 | res.send(users.map((user) => ` - ${user.name}\n`).join("")); 31 | }, 32 | 33 | json() { 34 | res.json(users); 35 | }, 36 | }); 37 | }); 38 | 39 | // or you could write a tiny middleware like 40 | // this to add a layer of abstraction 41 | // and make things a bit more declarative: 42 | async function format(path: string) { 43 | const obj = await import(path); 44 | 45 | return function (_req: OpineRequest, res: OpineResponse) { 46 | res.format(obj); 47 | }; 48 | } 49 | 50 | app.get("/users", await format("./users.ts")); 51 | 52 | if (import.meta.main) { 53 | app.listen(3000); 54 | console.log("Opine started on port 3000"); 55 | console.log("Try opening http://localhost:3000/ in the browser"); 56 | console.log( 57 | "Try: `curl -X GET http://localhost:3000 -H 'Accept: text/plain'`", 58 | ); 59 | console.log( 60 | "Try: `curl -X GET http://localhost:3000 -H 'Accept: application/json'`", 61 | ); 62 | } 63 | 64 | export { app }; 65 | -------------------------------------------------------------------------------- /examples/content-negotiation/users.ts: -------------------------------------------------------------------------------- 1 | import { users } from "./db.ts"; 2 | import type { OpineRequest, OpineResponse } from "../../src/types.ts"; 3 | 4 | export const html = (_req: OpineRequest, res: OpineResponse) => { 5 | res.send( 6 | `
    ${users.map((user) => `
  • ${user.name}
  • `).join("")}
`, 7 | ); 8 | }; 9 | 10 | export const text = (_req: OpineRequest, res: OpineResponse) => { 11 | res.send(users.map((user) => ` - ${user.name}\n`).join("")); 12 | }; 13 | 14 | export const json = (_req: OpineRequest, res: OpineResponse) => { 15 | res.json(users); 16 | }; 17 | -------------------------------------------------------------------------------- /examples/cors/README.md: -------------------------------------------------------------------------------- 1 | # cors 2 | 3 | An example of using CORS with Opine. 4 | 5 | ## How to run this example 6 | 7 | Run this example using: 8 | 9 | ```bash 10 | deno run --allow-read --allow-net ./examples/cors/index.ts 11 | ``` 12 | 13 | ## Further Documentation 14 | 15 | Please refer to: 16 | 17 | - [Deno cors module README.md](https://deno.land/x/cors@v1.2.2) 18 | - [Deno cors module Opine examples](https://deno.land/x/cors@v1.2.2/examples/opine) 19 | -------------------------------------------------------------------------------- /examples/cors/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("cors", () => { 6 | it("should respond with 'Hello Deno!' on the root path", async () => { 7 | await superdeno(app) 8 | .get("/") 9 | .expect(200, "Hello Deno!"); 10 | }); 11 | 12 | it("should apply CORS headers to the response", async () => { 13 | await superdeno(app) 14 | .get("/") 15 | .expect("Access-Control-Allow-Origin", "*"); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /examples/cors/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-read --allow-net ./examples/cors/index.ts 5 | */ 6 | 7 | import opine from "../../mod.ts"; 8 | import { opineCors } from "https://deno.land/x/cors@v1.2.2/mod.ts"; 9 | 10 | const app = opine(); 11 | 12 | app.use(opineCors()); 13 | 14 | app.get("/", function (_, res) { 15 | res.send("Hello Deno!"); 16 | }); 17 | 18 | if (import.meta.main) { 19 | app.listen(3000, () => console.log("Opine started on port 3000")); 20 | } 21 | 22 | export { app }; 23 | -------------------------------------------------------------------------------- /examples/dejs/README.md: -------------------------------------------------------------------------------- 1 | # ejs 2 | 3 | Example of how to use Opine's template engine and rendering capabilities with 4 | the `dejs` module. 5 | 6 | ## How to run this example 7 | 8 | Run this example using: 9 | 10 | ```bash 11 | deno run --allow-net --allow-read ./examples/dejs/index.ts 12 | ``` 13 | 14 | after cloning the repo locally. 15 | -------------------------------------------------------------------------------- /examples/dejs/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("dejs", () => { 6 | it("should render an ejs template on the root path", async () => { 7 | await superdeno(app) 8 | .get("/") 9 | .expect( 10 | 200, 11 | '\n\n\n\n \n \n EJS example\n \n\n\n\n\n

Users

\n
    \n \n
  • Deno <deno@denoland.com>
  • \n \n
  • SuperDeno <superdeno@denoland.com>
  • \n \n
\n\n\n\n\n', 12 | ); 13 | }); 14 | 15 | it("should set a cache-control header", async () => { 16 | await superdeno(app) 17 | .get("/") 18 | .expect("cache-control", "no-store"); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /examples/dejs/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/dejs/index.ts 5 | * 6 | * after cloning the repo locally. 7 | */ 8 | 9 | import { opine, serveStatic } from "../../mod.ts"; 10 | import { renderFileToString } from "https://deno.land/x/dejs@0.10.2/mod.ts"; 11 | import { dirname, join } from "../../deps.ts"; 12 | 13 | const app = opine(); 14 | const __dirname = dirname(import.meta.url); 15 | 16 | // Register ejs as .html. 17 | app.engine(".html", renderFileToString); 18 | 19 | // Optional since opine defaults to CWD/views 20 | app.set("views", join(__dirname, "views")); 21 | 22 | // Path to our public directory 23 | app.use(serveStatic(join(__dirname, "public"))); 24 | 25 | // Without this you would need to 26 | // supply the extension to res.render() 27 | // ex: res.render('users.html'). 28 | app.set("view engine", "html"); 29 | 30 | // Dummy users 31 | const users = [ 32 | { name: "Deno", email: "deno@denoland.com" }, 33 | { name: "SuperDeno", email: "superdeno@denoland.com" }, 34 | ]; 35 | 36 | app.get("/", (_req, res) => { 37 | res.set("cache-control", "no-store").render("users", { 38 | users, 39 | title: "EJS example", 40 | header: "Some users", 41 | }); 42 | }); 43 | 44 | if (import.meta.main) { 45 | app.listen(3000); 46 | console.log("Opine started on port 3000"); 47 | } 48 | 49 | export { app }; 50 | -------------------------------------------------------------------------------- /examples/dejs/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 25px; 3 | font-size: 14px; 4 | font-family: "Helvetica Nueue", "Lucida Grande", Arial, sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /examples/dejs/views/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /examples/dejs/views/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= title %> 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/dejs/views/users.html: -------------------------------------------------------------------------------- 1 | 6 | <%- await include('examples/dejs/views/header.html', { title }); %> 7 | 8 |

Users

9 |
    10 | <% users.forEach(function(user){ %> 11 |
  • <%= user.name %> <<%= user.email %>>
  • 12 | <% }) %> 13 |
14 | 15 | <%- await include('examples/dejs/views/footer.html'); %> 16 | -------------------------------------------------------------------------------- /examples/downloads/README.md: -------------------------------------------------------------------------------- 1 | # downloads 2 | 3 | Dummy file index server using path matching patterns and `res.download()` to 4 | serve files to the user. 5 | 6 | ## How to run this example 7 | 8 | Run this example using: 9 | 10 | ```bash 11 | deno run --allow-net --allow-read ./examples/downloads/index.ts 12 | ``` 13 | 14 | after cloning the repo locally. 15 | -------------------------------------------------------------------------------- /examples/downloads/files/CCTV大赛上海分赛区.txt: -------------------------------------------------------------------------------- 1 | Only for test. 2 | The file name is faked. -------------------------------------------------------------------------------- /examples/downloads/files/amazing.txt: -------------------------------------------------------------------------------- 1 | what an amazing download -------------------------------------------------------------------------------- /examples/downloads/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("downloads", () => { 6 | describe("GET /", async () => { 7 | it("should have a link to amazing.txt", async () => { 8 | await superdeno(app) 9 | .get("/") 10 | .expect(/href="\/files\/amazing.txt"/); 11 | }); 12 | }); 13 | 14 | describe("GET /files/amazing.txt", () => { 15 | it("should have a download header", async () => { 16 | await superdeno(app) 17 | .get("/files/amazing.txt") 18 | .expect("Content-Disposition", 'attachment; filename="amazing.txt"') 19 | .expect(200); 20 | }); 21 | }); 22 | 23 | describe("GET /files/missing.txt", () => { 24 | it("should respond with 404", async () => { 25 | await superdeno(app) 26 | .get("/files/missing.txt") 27 | .expect(404); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /examples/downloads/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/downloads/index.ts 5 | * 6 | * after cloning the repo locally. 7 | */ 8 | 9 | import { dirname, join } from "../../deps.ts"; 10 | import opine from "../../mod.ts"; 11 | 12 | const app = opine(); 13 | const __dirname = dirname(import.meta.url); 14 | 15 | app.get("/", function (_req, res) { 16 | res.send( 17 | "", 22 | ); 23 | }); 24 | 25 | // If we used `/files/*`, the name could be accessed via req.params[0] 26 | // but here we have named it using :file 27 | app.get("/files/:file(*)", async function (req, res, next) { 28 | const filePath = join(__dirname, "files", req.params.file); 29 | 30 | try { 31 | await res.download(filePath); 32 | } catch (err) { 33 | // file for download not found 34 | if (err instanceof Deno.errors.NotFound) { 35 | res.status = 404; 36 | res.send("Cant find that file, sorry!"); 37 | 38 | return; 39 | } 40 | 41 | // non-404 error 42 | return next(err); 43 | } 44 | }); 45 | 46 | if (import.meta.main) { 47 | // You can call listen the same as Express with just 48 | // a port: `app.listen(3000)`, or with any arguments 49 | // that the Deno `http.serve` methods accept. Namely 50 | // an address string, HttpOptions or HttpsOptions 51 | // objects. 52 | app.listen({ port: 3000 }); 53 | console.log("Opine started on port 3000"); 54 | } 55 | 56 | export { app }; 57 | -------------------------------------------------------------------------------- /examples/error-pages/README.md: -------------------------------------------------------------------------------- 1 | # error-pages 2 | 3 | Example of how to use Opine's template engine and rendering capabilities for 4 | custom error pages. 5 | 6 | ## How to run this example 7 | 8 | Run this example using: 9 | 10 | ```bash 11 | deno run --allow-net --allow-read --allow-env ./examples/error-pages/index.ts 12 | ``` 13 | 14 | after cloning the repo locally. 15 | 16 | To turn off verbose errors, set `DENO_ENV=production`. For example: 17 | 18 | ```bash 19 | DENO_ENV=production deno run --allow-net --allow-read --allow-env ./examples/error-pages/index.ts 20 | ``` 21 | -------------------------------------------------------------------------------- /examples/error-pages/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("error-pages", () => { 6 | describe("GET /", function () { 7 | it("should respond with page list", function (done) { 8 | superdeno(app) 9 | .get("/") 10 | .expect(/Pages Example/, done); 11 | }); 12 | }); 13 | 14 | describe("Accept: text/html", function () { 15 | describe("GET /403", function () { 16 | it("should respond with 403", function (done) { 17 | superdeno(app) 18 | .get("/403") 19 | .expect(403, done); 20 | }); 21 | }); 22 | 23 | describe("GET /404", function () { 24 | it("should respond with 404", function (done) { 25 | superdeno(app) 26 | .get("/404") 27 | .expect(404, done); 28 | }); 29 | }); 30 | 31 | describe("GET /500", function () { 32 | it("should respond with 500", function (done) { 33 | superdeno(app) 34 | .get("/500") 35 | .expect(500, done); 36 | }); 37 | }); 38 | }); 39 | 40 | describe("Accept: application/json", function () { 41 | describe("GET /403", function () { 42 | it("should respond with 403", function (done) { 43 | superdeno(app) 44 | .get("/403") 45 | .set("Accept", "application/json") 46 | .expect(403, done); 47 | }); 48 | }); 49 | 50 | describe("GET /404", function () { 51 | it("should respond with 404", function (done) { 52 | superdeno(app) 53 | .get("/404") 54 | .set("Accept", "application/json") 55 | .expect(404, { error: "Not Found" }, done); 56 | }); 57 | }); 58 | 59 | describe("GET /500", function () { 60 | it("should respond with 500", function (done) { 61 | superdeno(app) 62 | .get("/500") 63 | .set("Accept", "application/json") 64 | .expect(500, done); 65 | }); 66 | }); 67 | }); 68 | 69 | describe("Accept: text/plain", function () { 70 | describe("GET /403", function () { 71 | it("should respond with 403", function (done) { 72 | superdeno(app) 73 | .get("/403") 74 | .set("Accept", "text/plain") 75 | .expect(403, done); 76 | }); 77 | }); 78 | 79 | describe("GET /404", function () { 80 | it("should respond with 404", function (done) { 81 | superdeno(app) 82 | .get("/404") 83 | .set("Accept", "text/plain") 84 | .expect(404) 85 | .expect("Not Found", done); 86 | }); 87 | }); 88 | 89 | describe("GET /500", function () { 90 | it("should respond with 500", function (done) { 91 | superdeno(app) 92 | .get("/500") 93 | .set("Accept", "text/plain") 94 | .expect(500, done); 95 | }); 96 | }); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /examples/error-pages/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 25px; 3 | font-size: 14px; 4 | font-family: "Helvetica Nueue", "Lucida Grande", Arial, sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /examples/error-pages/views/404.ejs: -------------------------------------------------------------------------------- 1 | <%- await include("examples/error-pages/views/error_header.ejs"); %> 2 |

Cannot find <%= url %>

3 | <%- await include("examples/error-pages/views/footer.ejs"); %> 4 | -------------------------------------------------------------------------------- /examples/error-pages/views/500.ejs: -------------------------------------------------------------------------------- 1 | <%- await include("examples/error-pages/views/error_header.ejs"); %> 2 |

Error: <%= error.message %>

3 | <% if (settings['verbose errors']) { %> 4 |
<%= error.stack %>
5 | <% } else { %> 6 |

An error occurred!

7 | <% } %> 8 | <%- await include("examples/error-pages/views/footer.ejs"); %> 9 | -------------------------------------------------------------------------------- /examples/error-pages/views/error_header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Error 7 | 8 | 9 | 10 | 11 |

An error occurred!

12 | -------------------------------------------------------------------------------- /examples/error-pages/views/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /examples/error-pages/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Custom Pages Example 7 | 8 | 9 | 10 | 11 |

My Site

12 |

Pages Example

13 | 14 |
    15 |
  • visit 500
  • 16 |
  • visit 404
  • 17 |
  • visit 403
  • 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/error/README.md: -------------------------------------------------------------------------------- 1 | # error 2 | 3 | An example of how to use and write error middleware. 4 | 5 | ## How to run this example 6 | 7 | Run this example using: 8 | 9 | ```bash 10 | deno run --allow-net --allow-read ./examples/error/index.ts 11 | ``` 12 | 13 | if have the repo cloned locally _OR_ 14 | 15 | ```bash 16 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/error/index.ts 17 | ``` 18 | 19 | if you don't! 20 | -------------------------------------------------------------------------------- /examples/error/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("error", () => { 6 | describe("GET /", function () { 7 | it("should respond with 500", function (done) { 8 | superdeno(app) 9 | .get("/") 10 | .expect(500, done); 11 | }); 12 | }); 13 | 14 | describe("GET /next", function () { 15 | it("should respond with 500", function (done) { 16 | superdeno(app) 17 | .get("/next") 18 | .expect(500, done); 19 | }); 20 | }); 21 | 22 | describe("GET /missing", function () { 23 | it("should respond with 404", function (done) { 24 | superdeno(app) 25 | .get("/missing") 26 | .expect(404, done); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /examples/error/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/error/index.ts 5 | * 6 | * if have the repo cloned locally _OR_ 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/error/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import opine from "../../mod.ts"; 14 | import type { 15 | NextFunction, 16 | OpineRequest, 17 | OpineResponse, 18 | } from "../../src/types.ts"; 19 | 20 | const app = opine(); 21 | 22 | // error handling middleware have an arity of 4 23 | // instead of the typical (req, res, next), 24 | // otherwise they behave exactly like regular 25 | // middleware, you may have several of them, 26 | // in different orders etc. 27 | 28 | function error( 29 | err: any, 30 | _req: OpineRequest, 31 | res: OpineResponse, 32 | _next: NextFunction, 33 | ) { 34 | // respond with custom 500 "Internal Server Error". 35 | res.setStatus(500); 36 | res.json({ message: "Internal Server Error", error: err.message }); 37 | } 38 | 39 | app.get("/", function () { 40 | // Caught and passed down to the errorHandler middleware 41 | throw new Error("sync error"); 42 | }); 43 | 44 | app.get("/next", function (_req, _res, next) { 45 | // We can also pass exceptions to next() 46 | // The reason for setTimeout() is to show that 47 | // next() can be called inside an async operation, 48 | // in real life it can be a DB read or HTTP request. 49 | setTimeout(function () { 50 | next(new Error("async error")); 51 | }); 52 | }); 53 | 54 | // the error handler is placed after routes 55 | // if it were above it would not receive errors 56 | // from app.get() etc 57 | app.use(error); 58 | 59 | if (import.meta.main) { 60 | // You can call listen the same as Express with just 61 | // a port: `app.listen(3000)`, or with any arguments 62 | // that the Deno `http.serve` methods accept. Namely 63 | // an address string, HttpOptions or HttpsOptions 64 | // objects. 65 | app.listen({ port: 3000 }); 66 | console.log("Opine started on port 3000"); 67 | } 68 | 69 | export { app }; 70 | -------------------------------------------------------------------------------- /examples/eta/README.md: -------------------------------------------------------------------------------- 1 | # eta 2 | 3 | Example of how to use Opine's template engine and rendering capabilities with 4 | the `eta` template engine. 5 | 6 | View Eta's documentation at 7 | 8 | ## How to run this example 9 | 10 | Run this example using: 11 | 12 | ```bash 13 | deno run --allow-net --allow-read --unstable ./examples/eta/index.ts 14 | ``` 15 | -------------------------------------------------------------------------------- /examples/eta/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("eta", () => { 6 | it("should render an eta template on the root path", async () => { 7 | await superdeno(app) 8 | .get("/") 9 | .expect( 10 | 200, 11 | '\n\n \n \n\nEta example\n\n \n \n \n
\n

Users

\n
    \n
  • Deno <deno@denoland.com>
  • \n
  • SuperDeno <superdeno@denoland.com>
  • \n
  • Deno the Dinosaur <denosaur@denoland.com>
  • \n
\n
\n
This is a footer!
\n \n\n', 12 | ); 13 | }); 14 | 15 | it("should set a cache-control header", async () => { 16 | await superdeno(app) 17 | .get("/") 18 | .expect("cache-control", "no-store"); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /examples/eta/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read --unstable ./examples/eta/index.ts 5 | * 6 | * Note: you have to use --unstable because Eta uses the std 'fs' module (which requires --unstable) 7 | */ 8 | 9 | import { opine, serveStatic } from "../../mod.ts"; 10 | import { renderFile } from "https://deno.land/x/eta@v1.12.3/mod.ts"; 11 | import { dirname, join } from "../../deps.ts"; 12 | 13 | const app = opine(); 14 | const __dirname = dirname(import.meta.url); 15 | 16 | // Register Eta as .html. 17 | app.engine(".html", renderFile); 18 | 19 | // Optional since opine defaults to CWD/views 20 | app.set("views", join(__dirname, "views")); 21 | 22 | // Path to our public directory 23 | app.use(serveStatic(join(__dirname, "public"))); 24 | 25 | // Without this you would need to 26 | // supply the extension to res.render() 27 | // ex: res.render('users.html'). 28 | app.set("view engine", "html"); 29 | 30 | // Disable caching (comment out in production) 31 | app.set("view cache", false); 32 | 33 | // Dummy users 34 | const users = [ 35 | { name: "Deno", email: "deno@denoland.com" }, 36 | { name: "SuperDeno", email: "superdeno@denoland.com" }, 37 | { name: "Deno the Dinosaur", email: "denosaur@denoland.com" }, 38 | ]; 39 | 40 | app.get("/", (req, res) => { 41 | res.set("cache-control", "no-store").render("index", { 42 | users, 43 | title: "Eta example", 44 | header: "Some users", 45 | }); 46 | }); 47 | 48 | if (import.meta.main) { 49 | app.listen(3000); 50 | console.log("Opine started on port 3000"); 51 | } 52 | 53 | export { app }; 54 | -------------------------------------------------------------------------------- /examples/eta/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 25px; 3 | font-size: 14px; 4 | font-family: "Helvetica Nueue", "Lucida Grande", Arial, sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /examples/eta/views/footer.html: -------------------------------------------------------------------------------- 1 |
This is a footer!
2 | -------------------------------------------------------------------------------- /examples/eta/views/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= it.title %> 4 | 5 | -------------------------------------------------------------------------------- /examples/eta/views/index.html: -------------------------------------------------------------------------------- 1 | <% layout('./layout.html') %> 2 | 3 |
4 | <%~ includeFile("users.html", it) %> 5 |
6 | <%~ includeFile("footer.html") %> 7 | -------------------------------------------------------------------------------- /examples/eta/views/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%~ includeFile("header.html", {title: it.title}) %> 5 | 6 | 7 | <%~ it.body %> 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/eta/views/users.html: -------------------------------------------------------------------------------- 1 |

Users

2 |
    3 | <% it.users.forEach(function(user){ %> 4 | <% /* Note that we can simply put the HTML tags inside the string, 5 | rather than escaping them with < and > 6 | This is because Eta automatically escapes interpolations */ %> 7 |
  • <%= user.name %> <%= "<" + user.email + ">" %>
  • 8 | <% }) %> 9 |
10 | -------------------------------------------------------------------------------- /examples/graphql/README.md: -------------------------------------------------------------------------------- 1 | # graphql 2 | 3 | Example of how to use Opine with [gql](https://github.com/deno-libs/gql) for a 4 | simple GraphQL server. 5 | 6 | ## How to run this example 7 | 8 | ```sh 9 | deno run --allow-net --allow-read ./examples/graphql/index.ts 10 | ``` 11 | 12 | if have the repo cloned locally OR 13 | 14 | ```sh 15 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/graphql/index.ts 16 | ``` 17 | 18 | if you don't! 19 | 20 | Then try: 21 | 22 | 1. Opening a GraphQL playground on http//localhost:3000/graphql 23 | 2. Writing a query: 24 | 25 | ```graphql 26 | { 27 | hello 28 | } 29 | ``` 30 | 31 | 3. You will get this: 32 | 33 | ```json 34 | { 35 | "data": { 36 | "hello": "Hello World!" 37 | } 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /examples/graphql/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("graphql", () => { 6 | it("should respond to post requests on the graphql route", async () => { 7 | await superdeno(app) 8 | .post("/graphql") 9 | .send({ "query": "{ hello }" }) 10 | .expect(200, `{\n "data": {\n "hello": "Hello World!"\n }\n}`); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /examples/graphql/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/graphql/index.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/graphql/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import { opine } from "../../mod.ts"; 14 | import { GraphQLHTTP } from "https://deno.land/x/gql@0.2.0/mod.ts"; 15 | import { makeExecutableSchema } from "https://deno.land/x/graphql_tools@0.0.2/mod.ts"; 16 | import { gql } from "https://deno.land/x/graphql_tag@0.0.1/mod.ts"; 17 | 18 | const typeDefs = gql` 19 | type Query { 20 | hello: String 21 | } 22 | `; 23 | 24 | const resolvers = { 25 | Query: { 26 | hello: () => `Hello World!`, 27 | }, 28 | }; 29 | 30 | const schema = makeExecutableSchema({ resolvers, typeDefs }); 31 | 32 | const app = opine(); 33 | 34 | app.use("/graphql", GraphQLHTTP({ schema, graphiql: true })); 35 | 36 | if (import.meta.main) { 37 | app.listen(3000); 38 | console.log("Opine started on port 3000"); 39 | } 40 | 41 | export { app }; 42 | -------------------------------------------------------------------------------- /examples/hello-world/README.md: -------------------------------------------------------------------------------- 1 | # hello-world 2 | 3 | A basic example of how to configure and start an Opine server. 4 | 5 | ## How to run this example 6 | 7 | Run this example using: 8 | 9 | ```bash 10 | deno run --allow-net --allow-read ./examples/hello-world/index.ts 11 | ``` 12 | 13 | if have the repo cloned locally _OR_ 14 | 15 | ```bash 16 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/hello-world/index.ts 17 | ``` 18 | 19 | if you don't! 20 | -------------------------------------------------------------------------------- /examples/hello-world/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("hello-world", () => { 6 | it("should respond with 'Hello Deno!' on the root path", (done) => { 7 | superdeno(app) 8 | .get("/") 9 | .expect(200, "Hello Deno!", done); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /examples/hello-world/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/hello-world/index.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/hello-world/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import opine from "../../mod.ts"; 14 | 15 | const app = opine(); 16 | 17 | app.get("/", function (req, res) { 18 | res.send("Hello Deno!"); 19 | }); 20 | 21 | if (import.meta.main) { 22 | // You can call listen the same as Express with just 23 | // a port: `app.listen(3000)`, or with any arguments 24 | // that the Deno `http.serve` methods accept. Namely 25 | // an address string, HttpOptions or HttpsOptions 26 | // objects. 27 | app.listen({ port: 3000 }); 28 | console.log("Opine started on port 3000"); 29 | } 30 | 31 | export { app }; 32 | -------------------------------------------------------------------------------- /examples/json/README.md: -------------------------------------------------------------------------------- 1 | # json 2 | 3 | An example of how to use the `json` body-parser middleware in your Opine 4 | applications. 5 | 6 | ## How to run this example 7 | 8 | Run this example using: 9 | 10 | ```bash 11 | deno run --allow-net --allow-read ./examples/json/index.ts 12 | ``` 13 | 14 | if have the repo cloned locally _OR_ 15 | 16 | ```bash 17 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/json/index.ts 18 | ``` 19 | 20 | if you don't! 21 | -------------------------------------------------------------------------------- /examples/json/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("json", () => { 6 | it("should respond to JSON POST with the provided JSON", (done) => { 7 | superdeno(app) 8 | .post("/") 9 | .set("Content-Type", "application/json") 10 | .send({ "message": "Hello Deno!" }) 11 | .expect(200, `{"message":"Hello Deno!"}`, done); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /examples/json/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/json/index.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/json/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import { json, opine } from "../../mod.ts"; 14 | 15 | const app = opine(); 16 | 17 | // Use the JSON body parser to set `req.parsedBody` to the 18 | // passed JSON payload. Opine also exposes this value on 19 | // the `req.body` property itself for convenience. 20 | app.use(json()); 21 | 22 | // Receive `POST` requests to `/` and return the passed 23 | // JSON body. 24 | app.post("/", function (req, res) { 25 | res.send(req.body); 26 | }); 27 | 28 | if (import.meta.main) { 29 | // You can call listen the same as Express with just 30 | // a port: `app.listen(3000)`, or with any arguments 31 | // that the Deno `http.serve` methods accept. Namely 32 | // an address string, HttpOptions or HttpsOptions 33 | // objects. 34 | app.listen({ port: 3000 }); 35 | console.log("Opine started on port 3000"); 36 | console.log("Try a `POST /` with any JSON payload!"); 37 | console.log( 38 | `curl -X POST http://localhost:3000/ -d '{"message": "Hello Deno!"}' -H "Content-Type: application/json"`, 39 | ); 40 | } 41 | 42 | export { app }; 43 | -------------------------------------------------------------------------------- /examples/location/README.md: -------------------------------------------------------------------------------- 1 | # location 2 | 3 | An example of how set the `Location` header using `res.location()` for a `301` 4 | redirect. 5 | 6 | ## How to run this example 7 | 8 | Run this example using: 9 | 10 | ```bash 11 | deno run --allow-net --allow-read ./examples/location/index.ts 12 | ``` 13 | 14 | if have the repo cloned locally _OR_ 15 | 16 | ```bash 17 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/location/index.ts 18 | ``` 19 | 20 | if you don't! 21 | -------------------------------------------------------------------------------- /examples/location/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("location", () => { 6 | it("should respond with 'Hello Deno!' on the /home path", (done) => { 7 | superdeno(app) 8 | .get("/home") 9 | .expect(200, "Hello Deno!", done); 10 | }); 11 | 12 | it("should redirect to the /home path from /redirect", (done) => { 13 | superdeno(app) 14 | .get("/redirect") 15 | .expect("Location", "/home") 16 | .expect(301, done); 17 | }); 18 | 19 | it("should respond with 'Hello Deno!' on the /redirect path once redirected", (done) => { 20 | superdeno(app) 21 | .get("/redirect") 22 | .redirects(1) 23 | .expect(200, "Hello Deno!", done); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/location/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/location/index.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/location/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import opine from "../../mod.ts"; 14 | 15 | const app = opine(); 16 | 17 | app.get("/home", function (_req, res) { 18 | res.send("Hello Deno!"); 19 | }); 20 | 21 | // We set the Location header using `res.location()` and then 22 | // send the response with a 301. 23 | // 24 | // To learn more about the Location header, please refer to 25 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location 26 | app.get("/redirect", function (_req, res) { 27 | res.location("/home").sendStatus(301); 28 | }); 29 | 30 | if (import.meta.main) { 31 | // You can call listen the same as Express with just 32 | // a port: `app.listen(3000)`, or with any arguments 33 | // that the Deno `http.serve` methods accept. Namely 34 | // an address string, HttpOptions or HttpsOptions 35 | // objects. 36 | app.listen({ port: 3000 }); 37 | console.log("Opine started on port 3000"); 38 | console.log("Try opening http://localhost:3000/redirect"); 39 | } 40 | 41 | export { app }; 42 | -------------------------------------------------------------------------------- /examples/multi-router/README.md: -------------------------------------------------------------------------------- 1 | # multi-router 2 | 3 | An example of how to use the Opine `Router` to mount several controllers onto a 4 | path within an application / API. 5 | 6 | ## How to run this example 7 | 8 | Run this example using: 9 | 10 | ```bash 11 | deno run --allow-net --allow-read ./examples/multi-router/index.ts 12 | ``` 13 | 14 | if have the repo cloned locally _OR_ 15 | 16 | ```bash 17 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/multi-router/index.ts 18 | ``` 19 | 20 | if you don't! 21 | -------------------------------------------------------------------------------- /examples/multi-router/controllers/APIv1.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "../../../mod.ts"; 2 | 3 | const APIv1 = Router(); 4 | 5 | APIv1.get("/", function (_req, res) { 6 | res.send("Hello from APIv1 root route."); 7 | }); 8 | 9 | APIv1.get("/users", function (_req, res) { 10 | res.send("List of APIv1 users."); 11 | }); 12 | 13 | export default APIv1; 14 | -------------------------------------------------------------------------------- /examples/multi-router/controllers/APIv2.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "../../../mod.ts"; 2 | 3 | const APIv1 = Router(); 4 | 5 | APIv1.get("/", function (req, res) { 6 | res.send("Hello from APIv2 root route."); 7 | }); 8 | 9 | APIv1.get("/users", function (req, res) { 10 | res.send("List of APIv2 users."); 11 | }); 12 | 13 | export default APIv1; 14 | -------------------------------------------------------------------------------- /examples/multi-router/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("multi-router", () => { 6 | describe("GET /", function () { 7 | it("should respond with root handler", function (done) { 8 | superdeno(app) 9 | .get("/") 10 | .expect(200, "Hello from root route.", done); 11 | }); 12 | }); 13 | 14 | describe("GET /api/v1/", function () { 15 | it("should respond with APIv1 root handler", function (done) { 16 | superdeno(app) 17 | .get("/api/v1/") 18 | .expect(200, "Hello from APIv1 root route.", done); 19 | }); 20 | }); 21 | 22 | describe("GET /api/v1/users", function () { 23 | it("should respond with users from APIv1", function (done) { 24 | superdeno(app) 25 | .get("/api/v1/users") 26 | .expect(200, "List of APIv1 users.", done); 27 | }); 28 | }); 29 | 30 | describe("GET /api/v2/", function () { 31 | it("should respond with APIv2 root handler", function (done) { 32 | superdeno(app) 33 | .get("/api/v2/") 34 | .expect(200, "Hello from APIv2 root route.", done); 35 | }); 36 | }); 37 | 38 | describe("GET /api/v2/users", function () { 39 | it("should respond with users from APIv2", function (done) { 40 | superdeno(app) 41 | .get("/api/v2/users") 42 | .expect(200, "List of APIv2 users.", done); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /examples/multi-router/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/multi-router/index.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/multi-router/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import opine from "../../mod.ts"; 14 | import APIv1 from "./controllers/APIv1.ts"; 15 | import APIv2 from "./controllers/APIv2.ts"; 16 | 17 | const app = opine(); 18 | 19 | app.use("/api/v1", APIv1); 20 | app.use("/api/v2", APIv2); 21 | 22 | app.get("/", function (req, res) { 23 | res.send("Hello from root route."); 24 | }); 25 | 26 | if (import.meta.main) { 27 | // You can call listen the same as Express with just 28 | // a port: `app.listen(3000)`, or with any arguments 29 | // that the Deno `http.serve` methods accept. Namely 30 | // an address string, HttpOptions or HttpsOptions 31 | // objects. 32 | app.listen({ port: 3000 }); 33 | console.log("Opine started on port 3000"); 34 | } 35 | 36 | export { app }; 37 | -------------------------------------------------------------------------------- /examples/mvc/README.md: -------------------------------------------------------------------------------- 1 | # mvc 2 | 3 | A basic MVC-style controllers example. 4 | 5 | ## How to run this example 6 | 7 | Run this example using: 8 | 9 | ```bash 10 | deno run --allow-net --allow-read ./examples/mvc/index.ts 11 | ``` 12 | -------------------------------------------------------------------------------- /examples/mvc/controllers/main/index.ts: -------------------------------------------------------------------------------- 1 | import type { OpineRequest, OpineResponse } from "../../../../src/types.ts"; 2 | 3 | export const index = function (_req: OpineRequest, res: OpineResponse) { 4 | res.redirect("/users"); 5 | }; 6 | -------------------------------------------------------------------------------- /examples/mvc/controllers/pet/index.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | import * as db from "../../db.ts"; 3 | import type { 4 | NextFunction, 5 | OpineRequest, 6 | OpineResponse, 7 | } from "../../../../src/types.ts"; 8 | 9 | export const before = function ( 10 | req: OpineRequest, 11 | _res: OpineResponse, 12 | next: NextFunction, 13 | ) { 14 | const pet = db.pets[parseInt(req.params.pet_id)]; 15 | if (!pet) return next("route"); 16 | (req as any).pet = pet; 17 | next(); 18 | }; 19 | 20 | export const show = function ( 21 | req: OpineRequest, 22 | res: OpineResponse, 23 | _next: NextFunction, 24 | ) { 25 | res.render("show", { pet: (req as any).pet }); 26 | }; 27 | 28 | export const edit = function ( 29 | req: OpineRequest, 30 | res: OpineResponse, 31 | _next: NextFunction, 32 | ) { 33 | res.render("edit", { pet: (req as any).pet }); 34 | }; 35 | 36 | export const update = function ( 37 | req: OpineRequest, 38 | res: OpineResponse, 39 | _next: NextFunction, 40 | ) { 41 | const body = req.body; 42 | (req as any).pet.name = body.pet.name; 43 | res.redirect(`/pet/${(req as any).pet.id}`); 44 | }; 45 | -------------------------------------------------------------------------------- /examples/mvc/controllers/pet/views/edit.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Edit <%= pet.name %> 8 | 9 | 10 |

<%= pet.name %>

11 |
12 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/mvc/controllers/pet/views/show.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= pet.name %> 8 | 9 | 10 |

<%= pet.name %> edit

11 |

You are viewing <%= pet.name %>

12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/mvc/controllers/user-pet/index.ts: -------------------------------------------------------------------------------- 1 | import * as db from "../../db.ts"; 2 | import type { 3 | NextFunction, 4 | OpineRequest, 5 | OpineResponse, 6 | } from "../../../../src/types.ts"; 7 | 8 | export const name = "pet"; 9 | export const prefix = "/user/:user_id"; 10 | 11 | export const create = function ( 12 | req: OpineRequest, 13 | res: OpineResponse, 14 | next: NextFunction, 15 | ) { 16 | const id = req.params.user_id; 17 | const user = db.users[parseInt(id)]; 18 | const body = req.body; 19 | if (!user) return next("route"); 20 | const pet: db.Pet = { name: body.pet.name }; 21 | pet.id = db.pets.push(pet) - 1; 22 | user.pets!.push(pet); 23 | res.redirect("/user/" + id); 24 | }; 25 | -------------------------------------------------------------------------------- /examples/mvc/controllers/user/index.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | import * as db from "../../db.ts"; 3 | import type { 4 | NextFunction, 5 | OpineRequest, 6 | OpineResponse, 7 | } from "../../../../src/types.ts"; 8 | 9 | export const before = function ( 10 | req: OpineRequest, 11 | _res: OpineResponse, 12 | next: NextFunction, 13 | ) { 14 | const id = req.params.user_id; 15 | if (!id) return next(); 16 | // Pretend to query a database... 17 | setTimeout(function () { 18 | (req as any).user = db.users[parseInt(id)]; 19 | // cant find that user 20 | if (!(req as any).user) return next("route"); 21 | // found it, move on to the routes 22 | next(); 23 | }); 24 | }; 25 | 26 | export const list = function ( 27 | _req: OpineRequest, 28 | res: OpineResponse, 29 | _next: NextFunction, 30 | ) { 31 | res.render("list", { users: db.users }); 32 | }; 33 | 34 | export const edit = function ( 35 | req: OpineRequest, 36 | res: OpineResponse, 37 | _next: NextFunction, 38 | ) { 39 | res.render("edit", { user: (req as any).user }); 40 | }; 41 | 42 | export const show = function ( 43 | req: OpineRequest, 44 | res: OpineResponse, 45 | _next: NextFunction, 46 | ) { 47 | res.render("show", { user: (req as any).user }); 48 | }; 49 | 50 | export const update = function ( 51 | req: OpineRequest, 52 | res: OpineResponse, 53 | _next: NextFunction, 54 | ) { 55 | const body = req.body; 56 | (req as any).user.name = body.user.name; 57 | res.redirect("/user/" + (req as any).user.id); 58 | }; 59 | -------------------------------------------------------------------------------- /examples/mvc/controllers/user/views/edit.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Edit <%= user.name %> 8 | 9 | 10 |

<%= user.name %>

11 |
12 | 15 | 16 | 17 |
18 | 19 |
20 | 23 | 24 | 25 |
26 | 27 | -------------------------------------------------------------------------------- /examples/mvc/controllers/user/views/list.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Users 8 | 9 | 10 |

Users

11 |

Click a user below to view their pets.

12 |
    13 | <% users.forEach(function(user) { %> 14 |
  • <%= user.name %>
  • 15 | <% }); %> 16 |
17 | 18 | -------------------------------------------------------------------------------- /examples/mvc/controllers/user/views/show.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= user.name %> 8 | 9 | 10 |

<%= user.name %> edit

11 | 12 | <% if (user.pets.length) { %> 13 |

View <%= user.name %>'s pets:

14 |
    15 | <% user.pets.forEach(function(pet) { %> 16 |
  • 17 | <%= pet.name %> 18 |
  • 19 | <% }); %> 20 |
21 | <% } else { %> 22 |

No pets!

23 | <% } %> 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/mvc/db.ts: -------------------------------------------------------------------------------- 1 | // Faux database 2 | 3 | export interface Pet { 4 | name?: string; 5 | id?: number; 6 | } 7 | 8 | export interface User { 9 | name?: string; 10 | pets?: Pet[]; 11 | id?: number; 12 | } 13 | 14 | export const pets: Pet[] = [ 15 | { name: "Smudge", id: 0 }, 16 | { name: "Tilly", id: 1 }, 17 | { name: "Georgie", id: 2 }, 18 | ]; 19 | 20 | export const users: User[] = [ 21 | { name: "C", pets: [], id: 0 }, 22 | { name: "H", pets: [pets[0]], id: 1 }, 23 | { name: "S", pets: [pets[1], pets[2]], id: 2 }, 24 | ]; 25 | -------------------------------------------------------------------------------- /examples/mvc/deps.ts: -------------------------------------------------------------------------------- 1 | export { renderFileToString } from "https://deno.land/x/dejs@0.10.2/mod.ts"; 2 | -------------------------------------------------------------------------------- /examples/mvc/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/mvc/index.ts 5 | */ 6 | 7 | import { opine, serveStatic, urlencoded } from "../../mod.ts"; 8 | import { dirname, join } from "../../deps.ts"; 9 | import { renderFileToString } from "./deps.ts"; 10 | import type { 11 | NextFunction, 12 | OpineRequest, 13 | OpineResponse, 14 | } from "../../src/types.ts"; 15 | import boot from "./lib/boot.ts"; 16 | 17 | const app = opine(); 18 | const __dirname = dirname(import.meta.url); 19 | 20 | // Set our default template engine to "ejs" 21 | // which prevents the need for using file extensions 22 | app.set("view engine", "ejs"); 23 | app.engine(".ejs", renderFileToString); 24 | 25 | // set views for error and 404 pages 26 | app.set("views", join(__dirname, "views")); 27 | 28 | // Serve static files 29 | app.use(serveStatic(join(__dirname, "public"))); 30 | 31 | // Parse request bodies (req.body) 32 | app.use(urlencoded({ extended: true })); 33 | 34 | // load controllers 35 | await boot(app); 36 | 37 | app.use( 38 | function ( 39 | err: Error, 40 | _req: OpineRequest, 41 | res: OpineResponse, 42 | _next: NextFunction, 43 | ) { 44 | console.error(err.stack); 45 | res.setStatus(500).render("5xx"); 46 | }, 47 | ); 48 | 49 | // assume 404 since no middleware responded 50 | app.use(function (req, res) { 51 | res.setStatus(404).render("404", { url: req.originalUrl }); 52 | }); 53 | 54 | if (import.meta.main) { 55 | app.listen(3000, () => console.log("Opine started on port 3000")); 56 | } 57 | 58 | export { app }; 59 | -------------------------------------------------------------------------------- /examples/mvc/lib/boot.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../../mod.ts"; 2 | import type { Application } from "../../../src/types.ts"; 3 | import { dirname, join } from "../../../deps.ts"; 4 | import { renderFileToString } from "../deps.ts"; 5 | 6 | const rootDirname = dirname(import.meta.url); 7 | 8 | export default async function (parent: Application) { 9 | const dir = new URL("controllers/", rootDirname); 10 | 11 | for (const { name: fileName } of Deno.readDirSync(dir)) { 12 | const filePath = new URL(`${fileName}/index.ts`, dir); 13 | const obj = await import(filePath.toString()); 14 | const name = obj.name ?? fileName; 15 | const prefix = obj.prefix ?? ""; 16 | const app = opine(); 17 | 18 | let handler; 19 | let method; 20 | let url; 21 | 22 | app.set("view engine", "ejs"); 23 | app.engine(".ejs", renderFileToString); 24 | app.set("views", join(rootDirname, "..", "controllers", name, "views")); 25 | 26 | // Generate routes based on the exported methods 27 | for (const key in obj) { 28 | // "Reserved" exports 29 | if (~["name", "prefix", "before"].indexOf(key)) continue; 30 | // Route exports 31 | switch (key) { 32 | case "show": 33 | method = "get"; 34 | url = `/${name}/:${name}_id`; 35 | break; 36 | case "list": 37 | method = "get"; 38 | url = `/${name}s`; 39 | break; 40 | case "edit": 41 | method = "get"; 42 | url = `/${name}/:${name}_id/edit`; 43 | break; 44 | case "update": 45 | method = "post"; 46 | url = `/${name}/:${name}_id`; 47 | break; 48 | case "create": 49 | method = "post"; 50 | url = `/${name}`; 51 | break; 52 | case "index": 53 | method = "get"; 54 | url = "/"; 55 | break; 56 | default: 57 | /* istanbul ignore next */ 58 | throw new Error(`unrecognized route: ${name}.${key}`); 59 | } 60 | 61 | // setup 62 | handler = obj[key]; 63 | url = prefix + url; 64 | 65 | // before middleware support 66 | if (obj.before) { 67 | (app as any)[method](url, obj.before, handler); 68 | } else { 69 | (app as any)[method](url, handler); 70 | } 71 | } 72 | 73 | // mount the app 74 | parent.use(app); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/mvc/public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 16px "Helvetica Neue", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #107aff; 8 | text-decoration: none; 9 | } 10 | 11 | a:hover { 12 | text-decoration: underline; 13 | } 14 | 15 | h1 a { 16 | font-size: 16px; 17 | } 18 | -------------------------------------------------------------------------------- /examples/mvc/views/404.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Not Found 7 | 8 | 9 | 10 |

404: Not Found

11 |

Sorry we can't find <%= url %>

12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/mvc/views/5xx.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Internal Server Error 7 | 8 | 9 | 10 |

500: Internal Server Error

11 |

Looks like something blew up!

12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/proxy/README.md: -------------------------------------------------------------------------------- 1 | # proxy 2 | 3 | Example using `opine-http-proxy` as a proxy middleware. 4 | 5 | ## How to run this example 6 | 7 | Run this example using: 8 | 9 | ```bash 10 | deno run --allow-net --allow-read ./examples/proxy/index.ts 11 | ``` 12 | 13 | if have the repo cloned locally _OR_ 14 | 15 | ```bash 16 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/proxy/index.ts 17 | ``` 18 | 19 | if you don't! 20 | -------------------------------------------------------------------------------- /examples/proxy/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("proxy", () => { 6 | it("should proxy all routes to the Opine GitHub page", async () => { 7 | await superdeno(app) 8 | .get("/") 9 | .expect("server", "GitHub.com") 10 | .expect( 11 | 200, 12 | /Minimalist web framework for Deno ported from ExpressJS./, 13 | ); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/proxy/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/proxy/index.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/proxy/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import opine from "../../mod.ts"; 14 | import { proxy } from "https://deno.land/x/opineHttpProxy@2.9.1/mod.ts"; 15 | 16 | const app = opine(); 17 | 18 | app.use(proxy("https://github.com/cmorten/opine")); 19 | 20 | if (import.meta.main) { 21 | app.listen({ port: 3000 }); 22 | console.log("Opine started on port 3000"); 23 | } 24 | 25 | export { app }; 26 | -------------------------------------------------------------------------------- /examples/raw/README.md: -------------------------------------------------------------------------------- 1 | # urlencoded 2 | 3 | An example of how to use the `raw` body-parser middleware in your Opine 4 | applications. 5 | 6 | ## How to run this example 7 | 8 | Run this example using: 9 | 10 | ```bash 11 | deno run --allow-net --allow-read ./examples/raw/index.ts 12 | ``` 13 | 14 | if have the repo cloned locally _OR_ 15 | 16 | ```bash 17 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/raw/index.ts 18 | ``` 19 | 20 | if you don't! 21 | -------------------------------------------------------------------------------- /examples/raw/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("raw", () => { 6 | it("should respond to raw POST with the provided data", (done) => { 7 | superdeno(app) 8 | .post("/") 9 | .set("Content-Type", "application/octet-stream") 10 | .send("rwaaarrr") 11 | .expect(200, "rwaaarrr", done); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /examples/raw/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/raw/index.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/raw/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import { opine, raw } from "../../mod.ts"; 14 | 15 | const app = opine(); 16 | 17 | // Use the raw body parser to set `req.parsedBody` to a 18 | // decoded raw body. Opine also exposes this value on 19 | // the `req.body` property itself for convenience. 20 | app.use(raw()); 21 | 22 | // Receive `POST` requests to `/` and return the decoded 23 | // raw body. 24 | app.post("/", function (req, res) { 25 | res.send(req.body); 26 | }); 27 | 28 | if (import.meta.main) { 29 | // You can call listen the same as Express with just 30 | // a port: `app.listen(3000)`, or with any arguments 31 | // that the Deno `http.serve` methods accept. Namely 32 | // an address string, HttpOptions or HttpsOptions 33 | // objects. 34 | app.listen({ port: 3000 }); 35 | console.log("Opine started on port 3000"); 36 | console.log("Try a `POST /` with any raw payload!"); 37 | console.log( 38 | `curl -X POST http://localhost:3000/ -d 'rwaaarrr' -H "Content-Type: application/octet-stream"`, 39 | ); 40 | } 41 | 42 | export { app }; 43 | -------------------------------------------------------------------------------- /examples/react/README.md: -------------------------------------------------------------------------------- 1 | # react 2 | 3 | **Note that this example no longer works with latest Deno as `Deno.emit` was 4 | removed from the core APIs.** 5 | 6 | An example of how you can use Opine with React. 7 | 8 | This example uses ejs templates to create the HTML markup, serves static assets 9 | for styles, and performs server-side rendering of the React application. The 10 | application itself makes use of experimental Suspense for data-fetching which 11 | demonstrates "render-as-you-fetch", retrieving data from an API the server 12 | hosts. 13 | 14 | ## How to run this example 15 | 16 | Run this example using: 17 | 18 | ```bash 19 | deno run --allow-net --allow-read --unstable ./examples/react/server.tsx 20 | ``` 21 | 22 | if have the repo cloned locally. Unfortunately template rendering does not 23 | currently support remote URLs for views, so this example cannot be run directly 24 | from this repo. 25 | -------------------------------------------------------------------------------- /examples/react/client.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | import React from "https://esm.sh/react@17.0.2?dev"; 3 | import ReactDOM from "https://esm.sh/react-dom@17.0.2?dev"; 4 | import { App } from "./components/App.tsx"; 5 | 6 | (ReactDOM as any).hydrate( 7 | , 8 | document.getElementById("root"), 9 | ); 10 | -------------------------------------------------------------------------------- /examples/react/components/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "https://esm.sh/react@17.0.2?dev"; 2 | import { Title } from "./Title.tsx"; 3 | import { List } from "./List.tsx"; 4 | 5 | export const App = ({ isServer = false }) => { 6 | if (isServer) { 7 | return ( 8 | <> 9 | 10 | <p className="app_loading">Loading Doggos...</p> 11 | </> 12 | ); 13 | } 14 | 15 | return ( 16 | <> 17 | <Title /> 18 | <React.Suspense 19 | fallback={<p className="app_loading">Loading Doggos...</p>} 20 | > 21 | <List /> 22 | </React.Suspense> 23 | </> 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /examples/react/components/List.tsx: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file ban-ts-comment 2 | import React from "https://esm.sh/react@17.0.2?dev"; 3 | import { fetchDoggos } from "../services/fetchDoggos.ts"; 4 | 5 | const doggoResource = fetchDoggos(); 6 | 7 | export const List = () => { 8 | const doggos = doggoResource.read(); 9 | 10 | return ( 11 | <section className="list_section"> 12 | <ol className="list_list"> 13 | {doggos.map(( 14 | doggo: { id: number; src: string; alt: string }, 15 | ) => ( 16 | <li className="list_tile" key={doggo.id}> 17 | <a className="list_card" href={doggo.src}> 18 | <div className="list_image_container"> 19 | <img 20 | className="list_image" 21 | src={doggo.src} 22 | alt={doggo.alt} 23 | // @ts-ignore 24 | loading="lazy" 25 | /> 26 | </div> 27 | <div className="list_description">{`ID: ${doggo.id}`}</div> 28 | </a> 29 | </li> 30 | ))} 31 | </ol> 32 | </section> 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /examples/react/components/Title.tsx: -------------------------------------------------------------------------------- 1 | import React from "https://esm.sh/react@17.0.2?dev"; 2 | 3 | export const Title = () => ( 4 | <header className="title_header"> 5 | <h1 className="title_text">Deno Doggo List</h1> 6 | </header> 7 | ); 8 | -------------------------------------------------------------------------------- /examples/react/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0px; 3 | margin: 0px; 4 | font-size: 14px; 5 | font-family: "Helvetica Nueue", "Lucida Grande", Arial, sans-serif; 6 | } 7 | 8 | .app_loading { 9 | width: 100%; 10 | padding: 20px 0; 11 | font-size: 16px; 12 | text-align: center; 13 | margin: 0 auto; 14 | } 15 | 16 | .title_header { 17 | background: #eee; 18 | padding: 20px 0; 19 | text-align: center; 20 | width: 100%; 21 | } 22 | 23 | .title_text { 24 | font-size: 24px; 25 | } 26 | 27 | .list_section { 28 | padding: 20px 0; 29 | } 30 | 31 | .list_list { 32 | box-sizing: border-box; 33 | list-style: none; 34 | display: flex; 35 | flex-wrap: wrap; 36 | padding: 0 40px; 37 | justify-content: center; 38 | } 39 | 40 | .list_tile { 41 | flex: 0 0 25%; 42 | max-width: 25%; 43 | min-width: 225px; 44 | position: relative; 45 | width: 100%; 46 | padding: 15px; 47 | box-sizing: border-box; 48 | } 49 | 50 | .list_card { 51 | position: relative; 52 | display: flex; 53 | flex-direction: column; 54 | min-width: 0; 55 | word-wrap: break-word; 56 | background-color: #fff; 57 | background-clip: border-box; 58 | border: 1px solid rgba(0, 0, 0, 0.125); 59 | border-radius: 0.25rem; 60 | box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); 61 | overflow: hidden; 62 | text-decoration: none; 63 | color: #000; 64 | } 65 | 66 | .list_image_container { 67 | position: relative; 68 | font-size: 0; 69 | display: block; 70 | } 71 | 72 | .list_image_container::before { 73 | content: ""; 74 | display: block; 75 | width: 100%; 76 | position: relative; 77 | padding-top: 56.3%; 78 | box-sizing: border-box; 79 | } 80 | 81 | .list_image { 82 | width: 100%; 83 | height: 100%; 84 | position: absolute; 85 | top: 0; 86 | } 87 | 88 | .list_description { 89 | flex: 1 1 auto; 90 | padding: 1.25rem; 91 | } 92 | -------------------------------------------------------------------------------- /examples/react/server.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read --unstable ./examples/react/server.tsx 5 | * 6 | * if have the repo cloned locally. Unfortunately template rendering does not 7 | * currently support remote URLs for views, so this example cannot be run directly 8 | * from this repo. 9 | */ 10 | import { opine, serveStatic } from "../../mod.ts"; 11 | import { dirname, join } from "../../deps.ts"; 12 | import { renderFileToString } from "https://deno.land/x/dejs@0.10.1/mod.ts"; 13 | import React from "https://esm.sh/react@17.0.2?dev"; 14 | import ReactDOMServer from "https://esm.sh/react-dom@17.0.2/server?dev"; 15 | import { App } from "./components/App.tsx"; 16 | 17 | /** 18 | * Create our client bundle - you could split this out into 19 | * a preprocessing step. 20 | */ 21 | 22 | const { diagnostics, files } = await Deno.emit( 23 | "./examples/react/client.tsx", 24 | { 25 | bundle: "module", 26 | compilerOptions: { 27 | lib: ["dom", "dom.iterable", "esnext"], 28 | }, 29 | }, 30 | ); 31 | 32 | if (diagnostics?.length) { 33 | console.log(diagnostics); 34 | } 35 | 36 | /** 37 | * Create our Opine server. 38 | */ 39 | const app = opine(); 40 | const __dirname = dirname(import.meta.url); 41 | 42 | // Register ejs as .html. 43 | app.engine(".html", renderFileToString); 44 | 45 | // Optional since opine defaults to CWD/views 46 | app.set("views", join(__dirname, "views")); 47 | 48 | // Path to our public directory 49 | app.use(serveStatic(join(__dirname, "public"))); 50 | 51 | // Without this you would need to 52 | // supply the extension to res.render() 53 | // ex: res.render('main.html'). 54 | app.set("view engine", "html"); 55 | 56 | /** 57 | * Implement the "doggos" API. In your apps you may wish to use 58 | * a MVC pattern. See examples such as "multi-router" for how you 59 | * can split your code into separate controllers. 60 | */ 61 | app.use("/api/v1/doggos", (req, res) => { 62 | const count = parseInt(req.query.count); 63 | const doggos = [...new Array(count)].map((_, index) => ({ 64 | id: index + 1, 65 | alt: "A cute doggo", 66 | src: `https://placedog.net/400/225?id=${index + 1}`, 67 | })); 68 | 69 | res.json(doggos); 70 | }); 71 | 72 | /** 73 | * Serve our client JS bundle. 74 | */ 75 | app.get("/scripts/client.js", async (req, res) => { 76 | const js = files["deno:///bundle.js"]; 77 | res.type("application/javascript").send(js); 78 | }); 79 | 80 | /** 81 | * Main route setup 82 | */ 83 | app.get("/", (req, res) => { 84 | const app = <App isServer={true} />; 85 | const content = (ReactDOMServer as any).renderToString(app); 86 | const scripts = `<script type="module" src="/scripts/client.js"></script>`; 87 | 88 | res.set("cache-control", "no-store").render("main", { 89 | content, 90 | scripts, 91 | title: "React Example", 92 | }); 93 | }); 94 | 95 | if (import.meta.main) { 96 | app.listen(3000); 97 | console.log("Opine started on port 3000"); 98 | } 99 | 100 | export { app }; 101 | -------------------------------------------------------------------------------- /examples/react/services/fetchDoggos.ts: -------------------------------------------------------------------------------- 1 | import { wrapPromise } from "./wrapPromise.ts"; 2 | 3 | const doggoApiUrl = "/api/v1/doggos?count=100"; 4 | 5 | export function fetchDoggos() { 6 | const promise = fetch(doggoApiUrl) 7 | .then((res) => res.json()); 8 | 9 | return wrapPromise(promise); 10 | } 11 | -------------------------------------------------------------------------------- /examples/react/services/wrapPromise.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | 3 | export function wrapPromise(promise: Promise<any>) { 4 | let status = "pending"; 5 | let response: any; 6 | 7 | const suspender = promise.then( 8 | (res) => { 9 | status = "success"; 10 | response = res; 11 | }, 12 | (err) => { 13 | status = "error"; 14 | response = err; 15 | }, 16 | ); 17 | 18 | const read = () => { 19 | switch (status) { 20 | case "pending": 21 | throw suspender; 22 | case "error": 23 | throw response; 24 | default: 25 | return response; 26 | } 27 | }; 28 | 29 | return { read }; 30 | } 31 | 32 | export default wrapPromise; 33 | -------------------------------------------------------------------------------- /examples/react/views/footer.html: -------------------------------------------------------------------------------- 1 | </body> 2 | </html> 3 | -------------------------------------------------------------------------------- /examples/react/views/header.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="utf-8"> 5 | <meta name="viewport" content="width=device-width,initial-scale=1"> 6 | <title><%= title %> 7 | 8 | <%- scripts %> 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/react/views/main.html: -------------------------------------------------------------------------------- 1 | <%- await include('examples/react/views/header.html', { title, scripts }); %> 2 |
<%- content %>
3 | <%- await include('examples/react/views/footer.html'); %> -------------------------------------------------------------------------------- /examples/redirect/README.md: -------------------------------------------------------------------------------- 1 | # redirect 2 | 3 | An example of how to redirect using `res.redirect`. 4 | 5 | ## How to run this example 6 | 7 | Run this example using: 8 | 9 | ```bash 10 | deno run --allow-net --allow-read ./examples/redirect/index.ts 11 | ``` 12 | 13 | if have the repo cloned locally _OR_ 14 | 15 | ```bash 16 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/redirect/index.ts 17 | ``` 18 | 19 | if you don't! 20 | -------------------------------------------------------------------------------- /examples/redirect/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("redirect", () => { 6 | describe("GET /home", () => { 7 | it("should respond with 'Hello Deno!' on the /home path", (done) => { 8 | superdeno(app) 9 | .get("/home") 10 | .expect(200, "Hello Deno!", done); 11 | }); 12 | }); 13 | 14 | describe("GET /redirect", () => { 15 | it("should redirect to the /home path from /redirect", (done) => { 16 | superdeno(app) 17 | .get("/redirect") 18 | .expect("Location", "/home?status=301") 19 | .expect(301, done); 20 | }); 21 | 22 | it("should respond with 'Hello Deno!' on the /redirect path once redirected", (done) => { 23 | superdeno(app) 24 | .get("/redirect") 25 | .redirects(1) 26 | .expect(200, "Hello Deno!", done); 27 | }); 28 | }); 29 | 30 | describe("GET /relative/redirect", () => { 31 | it("should redirect to the /home path from /relative/redirect", (done) => { 32 | superdeno(app) 33 | .get("/relative/redirect") 34 | .expect("Location", "../home?status=302") 35 | .expect(302, done); 36 | }); 37 | 38 | it("should respond with 'Hello Deno!' on the /relative/redirect path once redirected", (done) => { 39 | superdeno(app) 40 | .get("/relative/redirect") 41 | .redirects(1) 42 | .expect(200, "Hello Deno!", done); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /examples/redirect/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/redirect/index.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/redirect/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import opine from "../../mod.ts"; 14 | 15 | const app = opine(); 16 | 17 | app.get("/home", function (_req, res) { 18 | res.send("Hello Deno!"); 19 | }); 20 | 21 | // Redirects from `/redirect` to `/home` using a permanent redirect. 22 | // 23 | // To learn more about the Location header, please refer to 24 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location 25 | app.get("/redirect", function (_req, res) { 26 | const status = 301; 27 | res.redirect(status, `/home?status=${status}`); 28 | }); 29 | 30 | // Redirects from `/relative/redirect/` to `/home` using: 31 | // 1. a temporary redirect 32 | // 2. a relative location. 33 | app.get("/relative/redirect", function (_req, res) { 34 | res.redirect("../home?status=302"); 35 | }); 36 | 37 | if (import.meta.main) { 38 | // You can call listen the same as Express with just 39 | // a port: `app.listen(3000)`, or with any arguments 40 | // that the Deno `http.serve` methods accept. Namely 41 | // an address string, HttpOptions or HttpsOptions 42 | // objects. 43 | app.listen({ port: 3000 }); 44 | console.log("Opine started on port 3000"); 45 | console.log( 46 | `Try opening http://localhost:3000/redirect or http://localhost:3000/relative/redirect`, 47 | ); 48 | } 49 | 50 | export { app }; 51 | -------------------------------------------------------------------------------- /examples/static-files/README.md: -------------------------------------------------------------------------------- 1 | # static-files 2 | 3 | An example of how to serve static files to a user using the Opine `serveStatic` 4 | middleware. 5 | 6 | ## How to run this example 7 | 8 | Run this example using: 9 | 10 | ```bash 11 | deno run --allow-net --allow-read ./examples/static-files/index.ts 12 | ``` 13 | 14 | after cloning the repo locally. 15 | -------------------------------------------------------------------------------- /examples/static-files/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | const toOsNewlines = (str: string) => 6 | Deno.build.os === "windows" ? str.replaceAll("\n", "\r\n") : str; 7 | 8 | describe("static-files", () => { 9 | describe("`public` on /", () => { 10 | it("should serve hello.txt", async () => { 11 | await superdeno(app) 12 | .get("/hello.txt") 13 | .expect(200, "Hello Deno!"); 14 | }); 15 | 16 | it("should serve js/app.js", async () => { 17 | await superdeno(app) 18 | .get("/js/app.js") 19 | .expect(200, toOsNewlines("// app.js\n")); 20 | }); 21 | 22 | it("should serve js/helper.js", async () => { 23 | await superdeno(app) 24 | .get("/js/helper.js") 25 | .expect(200, toOsNewlines("// helper.js\n")); 26 | }); 27 | 28 | it("should serve css/style.css", async () => { 29 | await superdeno(app) 30 | .get("/css/style.css") 31 | .expect( 32 | 200, 33 | toOsNewlines( 34 | "/* style.css */\nbody {\n background: darkslategrey;\n}\n", 35 | ), 36 | ); 37 | }); 38 | }); 39 | 40 | describe("`public` on /static", () => { 41 | it("should serve hello.txt", async () => { 42 | await superdeno(app) 43 | .get("/static/hello.txt") 44 | .expect(200, "Hello Deno!"); 45 | }); 46 | 47 | it("should serve js/app.js", async () => { 48 | await superdeno(app) 49 | .get("/static/js/app.js") 50 | .expect(200, toOsNewlines("// app.js\n")); 51 | }); 52 | 53 | it("should serve js/helper.js", async () => { 54 | await superdeno(app) 55 | .get("/static/js/helper.js") 56 | .expect(200, toOsNewlines("// helper.js\n")); 57 | }); 58 | 59 | it("should serve css/style.css", async () => { 60 | await superdeno(app) 61 | .get("/static/css/style.css") 62 | .expect( 63 | 200, 64 | toOsNewlines( 65 | "/* style.css */\nbody {\n background: darkslategrey;\n}\n", 66 | ), 67 | ); 68 | }); 69 | }); 70 | 71 | describe("`public/css` on /", () => { 72 | it("should serve css/style.css", async () => { 73 | await superdeno(app) 74 | .get("/style.css") 75 | .expect( 76 | 200, 77 | toOsNewlines( 78 | "/* style.css */\nbody {\n background: darkslategrey;\n}\n", 79 | ), 80 | ); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /examples/static-files/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/static-files/index.ts 5 | * 6 | * after cloning the repo locally. 7 | */ 8 | 9 | import { dirname, join } from "../../deps.ts"; 10 | import { opine, serveStatic } from "../../mod.ts"; 11 | 12 | const app = opine(); 13 | const __dirname = dirname(import.meta.url); 14 | 15 | // Opine on its own has no notion 16 | // of a "file". The serveStatic() 17 | // middleware checks for a file matching 18 | // the `req.path` within the directory 19 | // that you pass it. In this case "GET /js/app.js" 20 | // will look for "./public/js/app.js". 21 | 22 | app.use(serveStatic(join(__dirname, "public"))); 23 | 24 | // If you wanted to "prefix" you may use 25 | // the mounting feature of serveStatic, for example 26 | // "GET /static/js/app.js" instead of "GET /js/app.js". 27 | // The mount-path "/static" is simply removed before 28 | // passing control to the serveStatic() middleware, 29 | // thus it serves the file correctly by ignoring "/static" 30 | app.use("/static", serveStatic(join(__dirname, "public"))); 31 | 32 | // If you want to serve files from 33 | // several directories, you can use serveStatic() 34 | // multiple times! Here we're passing "./public/css", 35 | // this will allow "GET /style.css" instead of "GET /css/style.css": 36 | app.use(serveStatic(join(__dirname, "public", "css"))); 37 | 38 | if (import.meta.main) { 39 | // You can call listen the same as Express with just 40 | // a port: `app.listen(3000)`, or with any arguments 41 | // that the Deno `http.serve` methods accept. Namely 42 | // an address string, HttpOptions or HttpsOptions 43 | // objects. 44 | app.listen({ port: 3000 }); 45 | 46 | console.log("listening on port 3000"); 47 | console.log("try:"); 48 | console.log(" GET /hello.txt"); 49 | console.log(" GET /js/app.js"); 50 | console.log(" GET /js/helper.js"); 51 | console.log(" GET /css/style.css"); 52 | console.log("try the files served under `static/`:"); 53 | console.log(" GET /static/hello.txt"); 54 | console.log(" GET /static/js/app.js"); 55 | console.log(" GET /static/js/helper.js"); 56 | console.log(" GET /static/css/style.css"); 57 | console.log("try the css served on the root:"); 58 | console.log(" GET /style.css"); 59 | } 60 | 61 | export { app }; 62 | -------------------------------------------------------------------------------- /examples/static-files/public/css/style.css: -------------------------------------------------------------------------------- 1 | /* style.css */ 2 | body { 3 | background: darkslategrey; 4 | } 5 | -------------------------------------------------------------------------------- /examples/static-files/public/hello.txt: -------------------------------------------------------------------------------- 1 | Hello Deno! -------------------------------------------------------------------------------- /examples/static-files/public/js/app.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | -------------------------------------------------------------------------------- /examples/static-files/public/js/helper.js: -------------------------------------------------------------------------------- 1 | // helper.js 2 | -------------------------------------------------------------------------------- /examples/text/README.md: -------------------------------------------------------------------------------- 1 | # urlencoded 2 | 3 | An example of how to use the `text` body-parser middleware in your Opine 4 | applications. 5 | 6 | ## How to run this example 7 | 8 | Run this example using: 9 | 10 | ```bash 11 | deno run --allow-net --allow-read ./examples/text/index.ts 12 | ``` 13 | 14 | if have the repo cloned locally _OR_ 15 | 16 | ```bash 17 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/text/index.ts 18 | ``` 19 | 20 | if you don't! 21 | -------------------------------------------------------------------------------- /examples/text/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("text", () => { 6 | it("should respond to text POST with the provided text", (done) => { 7 | superdeno(app) 8 | .post("/") 9 | .set("Content-Type", "text/plain") 10 | .send("Hello Deno!") 11 | .expect(200, "Hello Deno!", done); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /examples/text/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/text/index.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/text/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import { opine, text } from "../../mod.ts"; 14 | 15 | const app = opine(); 16 | 17 | // Use the text body parser to set `req.parsedBody` to a 18 | // decoded text body. Opine also exposes this value on 19 | // the `req.body` property itself for convenience. 20 | app.use(text()); 21 | 22 | // Receive `POST` requests to `/` and return the decoded 23 | // text body. 24 | app.post("/", function (req, res) { 25 | res.send(req.body); 26 | }); 27 | 28 | if (import.meta.main) { 29 | // You can call listen the same as Express with just 30 | // a port: `app.listen(3000)`, or with any arguments 31 | // that the Deno `http.serve` methods accept. Namely 32 | // an address string, HttpOptions or HttpsOptions 33 | // objects. 34 | app.listen({ port: 3000 }); 35 | console.log("Opine started on port 3000"); 36 | console.log("Try a `POST /` with any text payload!"); 37 | console.log( 38 | `curl -X POST http://localhost:3000/ -d 'Hello Deno!' -H "Content-Type: text/plain"`, 39 | ); 40 | } 41 | 42 | export { app }; 43 | -------------------------------------------------------------------------------- /examples/urlencoded/README.md: -------------------------------------------------------------------------------- 1 | # urlencoded 2 | 3 | An example of how to use the `urlencoded` body-parser middleware in your Opine 4 | applications. 5 | 6 | ## How to run this example 7 | 8 | Run this example using: 9 | 10 | ```bash 11 | deno run --allow-net --allow-read ./examples/urlencoded/index.ts 12 | ``` 13 | 14 | if have the repo cloned locally _OR_ 15 | 16 | ```bash 17 | deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/urlencoded/index.ts 18 | ``` 19 | 20 | if you don't! 21 | -------------------------------------------------------------------------------- /examples/urlencoded/index.test.ts: -------------------------------------------------------------------------------- 1 | import { superdeno } from "../../test/deps.ts"; 2 | import { describe, it } from "../../test/utils.ts"; 3 | import { app } from "./index.ts"; 4 | 5 | describe("urlencoded", () => { 6 | it("should respond to a urlencoded POST with the decoded payload as JSON", (done) => { 7 | superdeno(app) 8 | .post("/") 9 | .set("Content-Type", "application/x-www-form-urlencoded") 10 | .send("message%3Dhello%20world%26sender%3Ddeno") 11 | .expect(200, `{"message":"hello world","sender":"deno"}`, done); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /examples/urlencoded/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-net --allow-read ./examples/urlencoded/index.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-net --allow-read https://raw.githubusercontent.com/cmorten/opine/main/examples/urlencoded/index.ts 9 | * 10 | * if you don't! 11 | */ 12 | 13 | import { opine, urlencoded } from "../../mod.ts"; 14 | 15 | const app = opine(); 16 | 17 | // Use the urlencoded body parser to set `req.parsedBody` to a 18 | // decoded version of the passed payload. Opine also exposes 19 | // this value on the `req.body` property itself for convenience. 20 | app.use(urlencoded()); 21 | 22 | // Receive `POST` requests to `/` and return the decoded 23 | // version of the passed urlencoded body. 24 | app.post("/", function (req, res) { 25 | res.send(req.body); 26 | }); 27 | 28 | if (import.meta.main) { 29 | // You can call listen the same as Express with just 30 | // a port: `app.listen(3000)`, or with any arguments 31 | // that the Deno `http.serve` methods accept. Namely 32 | // an address string, HttpOptions or HttpsOptions 33 | // objects. 34 | app.listen({ port: 3000 }); 35 | console.log("Opine started on port 3000"); 36 | console.log("Try a `POST /` with any urlencoded payload!"); 37 | console.log( 38 | `curl -X POST http://localhost:3000/ -d 'message%3Dhello%20world%26sender%3Ddeno' -H "Content-Type: application/x-www-form-urlencoded"`, 39 | ); 40 | } 41 | 42 | export { app }; 43 | -------------------------------------------------------------------------------- /examples/websockets/client.ts: -------------------------------------------------------------------------------- 1 | console.log("Client started"); 2 | const socket = new WebSocket("ws://localhost:3000/ws"); 3 | 4 | socket.addEventListener("open", () => { 5 | socket.send("ping"); 6 | console.log("sent ping to server"); 7 | }); 8 | 9 | socket.addEventListener("close", (_) => { 10 | console.log("socket closed :("); 11 | }); 12 | 13 | socket.addEventListener("message", (e) => { 14 | if (e.data === "ping") { 15 | console.log("Received ping from server. Responding..."); 16 | socket.send("pong"); 17 | } else if (e.data === "pong") { 18 | console.log("Received ping response from server."); 19 | } 20 | }); 21 | 22 | socket.addEventListener("error", (e) => { 23 | console.error(`Had error`, e); 24 | }); 25 | -------------------------------------------------------------------------------- /examples/websockets/server.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this example using: 3 | * 4 | * deno run --allow-read --allow-net ./examples/websockets/server.ts 5 | * 6 | * if have the repo cloned locally OR 7 | * 8 | * deno run --allow-read --allow-net https://raw.githubusercontent.com/cmorten/opine/main/examples/websockets/server.ts 9 | * 10 | * if you don't! 11 | * 12 | * After running the example, run the client with: 13 | * 14 | * deno run --allow-read --allow-net ./examples/websockets/client.ts 15 | * 16 | * (OR) 17 | * 18 | * deno run --allow-read --allow-net https://raw.githubusercontent.com/cmorten/opine/main/examples/websockets/server.ts 19 | */ 20 | 21 | import { opine } from "../../mod.ts"; 22 | 23 | const app = opine(); 24 | const sockets = new Map(); 25 | 26 | const handleWs = (socket: WebSocket) => { 27 | sockets.set(crypto.randomUUID(), socket); 28 | 29 | socket.addEventListener("open", () => { 30 | socket.send("ping"); 31 | console.log("sent ping to client"); 32 | }); 33 | 34 | socket.addEventListener("close", (_) => { 35 | console.log("socket closed :("); 36 | }); 37 | 38 | socket.addEventListener("message", (e) => { 39 | if (e.data === "ping") { 40 | console.log("Received ping from client. Responding..."); 41 | socket.send("pong"); 42 | } else if (e.data === "pong") { 43 | console.log("Received ping response from client."); 44 | } 45 | }); 46 | }; 47 | 48 | app.get("/ws", async (req, res, next) => { 49 | if (req.headers.get("upgrade") === "websocket") { 50 | const sock = req.upgrade(); 51 | await handleWs(sock); 52 | } else { 53 | res.send("You've gotta set the magic header..."); 54 | } 55 | 56 | next(); 57 | }); 58 | 59 | app.use((_, res, __) => { 60 | res.setHeader("access-control-allow-origin", "*"); 61 | res.setHeader( 62 | "access-control-expose-headers", 63 | "Upgrade,sec-websocket-accept,connection", 64 | ); 65 | 66 | res.send(); 67 | }); 68 | 69 | app.listen(3000, () => { 70 | console.log("Opine listening on port 3000."); 71 | }); 72 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export { opine, opine as default, request, response } from "./src/opine.ts"; 2 | export { app as application } from "./src/application.ts"; 3 | export { methods } from "./src/methods.ts"; 4 | export { Router } from "./src/router/index.ts"; 5 | export { Route } from "./src/router/route.ts"; 6 | export * from "./src/types.ts"; 7 | export { query } from "./src/middleware/query.ts"; 8 | export { json } from "./src/middleware/bodyParser/json.ts"; 9 | export { raw } from "./src/middleware/bodyParser/raw.ts"; 10 | export { text } from "./src/middleware/bodyParser/text.ts"; 11 | export { urlencoded } from "./src/middleware/bodyParser/urlencoded.ts"; 12 | export { serveStatic } from "./src/middleware/serveStatic.ts"; 13 | export { DENO_SUPPORTED_VERSIONS, VERSION } from "./version.ts"; 14 | -------------------------------------------------------------------------------- /src/methods.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Supported Deno methods. 3 | * 4 | * @public 5 | */ 6 | export const methods: string[] = [ 7 | "get", 8 | "post", 9 | "put", 10 | "head", 11 | "delete", 12 | "options", 13 | // "trace", // As of Deno 1.9.0 - TypeError: Method is forbidden. 14 | "copy", 15 | "lock", 16 | "mkcol", 17 | "move", 18 | "purge", 19 | "propfind", 20 | "proppatch", 21 | "unlock", 22 | "report", 23 | "mkactivity", 24 | "checkout", 25 | "merge", 26 | "m-search", 27 | "notify", 28 | "subscribe", 29 | "unsubscribe", 30 | "patch", 31 | "search", 32 | "connect", 33 | ]; 34 | -------------------------------------------------------------------------------- /src/middleware/bodyParser/getCharset.ts: -------------------------------------------------------------------------------- 1 | import { charset } from "../../../deps.ts"; 2 | import type { OpineRequest } from "../../types.ts"; 3 | 4 | /** 5 | * Get the charset of a request. 6 | * 7 | * @param {OpineRequest} req 8 | * @returns {string|undefined} 9 | * @private 10 | */ 11 | export function getCharset(req: OpineRequest): string | undefined { 12 | try { 13 | return (charset(req.headers.get("Content-Type") || "") || "").toLowerCase(); 14 | } catch (_e) { 15 | return undefined; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/middleware/bodyParser/typeChecker.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Port of body-parser (https://github.com/expressjs/body-parser) for Deno. 3 | * 4 | * Licensed as follows: 5 | * 6 | * (The MIT License) 7 | * 8 | * Copyright (c) 2014 Jonathan Ong 9 | * Copyright (c) 2014-2015 Douglas Christopher Wilson 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining 12 | * a copy of this software and associated documentation files (the 13 | * 'Software'), to deal in the Software without restriction, including 14 | * without limitation the rights to use, copy, modify, merge, publish, 15 | * distribute, sublicense, and/or sell copies of the Software, and to 16 | * permit persons to whom the Software is furnished to do so, subject to 17 | * the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be 20 | * included in all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 23 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 25 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 26 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 27 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 28 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | * 30 | */ 31 | 32 | import type { OpineRequest } from "../../types.ts"; 33 | import { typeofrequest } from "../../../deps.ts"; 34 | 35 | /** 36 | * Get the simple type checker. 37 | * 38 | * @param {string} type 39 | * @return {function} 40 | */ 41 | export function typeChecker(type: string) { 42 | return function checkType(req: OpineRequest) { 43 | return Boolean(typeofrequest(req.headers, [type])); 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/middleware/init.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | NextFunction, 3 | Opine, 4 | OpineRequest, 5 | OpineResponse, 6 | } from "../../src/types.ts"; 7 | 8 | const create = Object.create; 9 | const setPrototypeOf = Object.setPrototypeOf; 10 | 11 | /** 12 | * Initialization middleware, exposing the 13 | * request and response to each other, setting 14 | * locals if not defined, as well as defaulting 15 | * the X-Powered-By header field. 16 | * 17 | * @param {Opine} app 18 | * @return {Function} init middleware 19 | * @private 20 | */ 21 | export const init = function (app: Opine) { 22 | return function opineInit( 23 | req: OpineRequest, 24 | res: OpineResponse, 25 | next: NextFunction, 26 | ) { 27 | if (app.enabled("x-powered-by")) res.set("X-Powered-By", "Opine"); 28 | 29 | req.res = res; 30 | res.req = req; 31 | req.next = next; 32 | 33 | setPrototypeOf(req, app.request); 34 | setPrototypeOf(res, app.response); 35 | 36 | // Deno 1.9.0 introduced a change which restricted the interaction with 37 | // the prototype object requiring properties to be manually copied in 38 | // this fashion. 39 | req.app = app.request.app; 40 | res.app = app.response.app; 41 | res.locals = res.locals || create(null); 42 | 43 | next(); 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /src/middleware/query.ts: -------------------------------------------------------------------------------- 1 | import { qs } from "../../deps.ts"; 2 | import { parseUrl } from "../utils/parseUrl.ts"; 3 | import { merge } from "../utils/merge.ts"; 4 | import type { NextFunction, OpineRequest, OpineResponse } from "../types.ts"; 5 | 6 | /** 7 | * Exposes a query object containing the querystring 8 | * parameters of the request url. 9 | * 10 | * @return {Function} query middleware 11 | * @public 12 | */ 13 | export const query = function (options: any) { 14 | let opts = merge({}, options); 15 | let queryParse = qs.parse; 16 | 17 | if (typeof options === "function") { 18 | queryParse = options; 19 | opts = undefined; 20 | } 21 | 22 | if (opts !== undefined && opts.allowPrototypes === undefined) { 23 | // back-compat for qs module 24 | opts.allowPrototypes = true; 25 | } 26 | 27 | return function opineQuery( 28 | req: OpineRequest, 29 | _res: OpineResponse, 30 | next: NextFunction, 31 | ) { 32 | if (!req.query) { 33 | const value = parseUrl(req)?.query as string; 34 | req.query = queryParse(value, opts); 35 | } 36 | 37 | next(); 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /src/opine.ts: -------------------------------------------------------------------------------- 1 | import { app as application } from "./application.ts"; 2 | import { WrappedRequest } from "./request.ts"; 3 | import { Response as ServerResponse } from "./response.ts"; 4 | import { mergeDescriptors } from "./utils/mergeDescriptors.ts"; 5 | import { EventEmitter } from "../deps.ts"; 6 | import type { 7 | NextFunction, 8 | Opine, 9 | OpineRequest, 10 | OpineResponse, 11 | } from "./types.ts"; 12 | 13 | /** 14 | * Response prototype. 15 | * 16 | * @public 17 | */ 18 | export const response: OpineResponse = Object.create(ServerResponse.prototype); 19 | export const request: OpineRequest = Object.create(WrappedRequest.prototype); 20 | 21 | /** 22 | * Create an Opine application. 23 | * 24 | * @return {Opine} 25 | * @public 26 | */ 27 | export function opine(): Opine { 28 | const app = function ( 29 | req: OpineRequest, 30 | res: OpineResponse = new ServerResponse(), 31 | next: NextFunction, 32 | ): void { 33 | app.handle(req, res, next); 34 | } as Opine; 35 | 36 | const eventEmitter = new EventEmitter(); 37 | 38 | app.emit = (event: string, ...args: any[]) => 39 | eventEmitter.emit(event, ...args); 40 | app.on = (event: string, arg: any) => eventEmitter.on(event, arg); 41 | 42 | mergeDescriptors(app, application, false); 43 | 44 | // expose the prototype that will get set on requests 45 | app.request = Object.create(request, { 46 | app: { configurable: true, enumerable: true, writable: true, value: app }, 47 | }); 48 | 49 | // expose the prototype that will get set on responses 50 | app.response = Object.create(response, { 51 | app: { configurable: true, enumerable: true, writable: true, value: app }, 52 | }); 53 | 54 | app.init(); 55 | 56 | return app; 57 | } 58 | -------------------------------------------------------------------------------- /src/utils/compileETag.ts: -------------------------------------------------------------------------------- 1 | import { etag as createETag } from "./etag.ts"; 2 | 3 | /** 4 | * Create an ETag generator function, generating ETags with 5 | * the given options. 6 | * 7 | * @param {object} options 8 | * @return {Function} generateETag function. 9 | * @private 10 | */ 11 | function createETagGenerator(options: any): Function { 12 | return function generateETag(body: string | Uint8Array | Deno.FileInfo) { 13 | return createETag(body, options); 14 | }; 15 | } 16 | 17 | /** 18 | * Return strong ETag for `body`. 19 | * 20 | * @param {any} body 21 | * @param {string} [encoding] 22 | * @return {string} 23 | * @private 24 | */ 25 | export const etag = createETagGenerator({ weak: false }); 26 | 27 | /** 28 | * Return weak ETag for `body`. 29 | * 30 | * @param {any} body 31 | * @param {string} [encoding] 32 | * @return {string} 33 | * @private 34 | */ 35 | export const wetag = createETagGenerator({ weak: true }); 36 | 37 | /** 38 | * Check if `path` looks absolute. 39 | * 40 | * @param {String} path 41 | * @return {Boolean} 42 | * @private 43 | */ 44 | export const compileETag = function (value: any) { 45 | let fn; 46 | 47 | if (typeof value === "function") { 48 | return value; 49 | } 50 | 51 | switch (value) { 52 | case true: 53 | fn = wetag; 54 | break; 55 | case false: 56 | break; 57 | case "strong": 58 | fn = etag; 59 | break; 60 | case "weak": 61 | fn = wetag; 62 | break; 63 | default: 64 | throw new TypeError(`unknown value for etag function: ${value}`); 65 | } 66 | 67 | return fn; 68 | }; 69 | -------------------------------------------------------------------------------- /src/utils/compileQueryParser.ts: -------------------------------------------------------------------------------- 1 | import { parse, qs } from "../../deps.ts"; 2 | 3 | /** 4 | * Return new empty object. 5 | * 6 | * @return {Object} 7 | * @api private 8 | */ 9 | function newObject() { 10 | return {}; 11 | } 12 | 13 | /** 14 | * Parse an extended query string with qs. 15 | * 16 | * @return {Object} 17 | * @private 18 | */ 19 | function parseExtendedQueryString(str: string) { 20 | return qs.parse(str, { 21 | allowPrototypes: true, 22 | }); 23 | } 24 | 25 | type QueryParserValue = Function | boolean | "extended" | "simple"; 26 | 27 | /** 28 | * Compile "query parser" value to function. 29 | * 30 | * @param {String|Boolean|Function} val 31 | * @return {Function} 32 | * @api private 33 | */ 34 | export function compileQueryParser(value: QueryParserValue): Function { 35 | if (typeof value === "function") { 36 | return value; 37 | } 38 | 39 | switch (value) { 40 | case true: 41 | return parse; 42 | case false: 43 | return newObject; 44 | case "extended": 45 | return parseExtendedQueryString; 46 | case "simple": 47 | return parse; 48 | default: 49 | throw new TypeError(`unknown value for query parser function: ${value}`); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/utils/compileTrust.ts: -------------------------------------------------------------------------------- 1 | import { compile } from "./proxyAddr.ts"; 2 | 3 | type TrustValue = Function | boolean | string | number | string[]; 4 | 5 | /** 6 | * Compile "proxy trust" value to function. 7 | * 8 | * @param {Boolean|String|Number|Array|Function} value 9 | * @return {Function} 10 | * @private 11 | */ 12 | export function compileTrust(value: TrustValue) { 13 | if (typeof value === "function") return value; 14 | 15 | if (value === true) { 16 | // Support plain true / false 17 | return function () { 18 | return true; 19 | }; 20 | } 21 | 22 | if (typeof value === "number") { 23 | // Support trusting hop count 24 | return function (_: unknown, i: number) { 25 | return i < (value as number); 26 | }; 27 | } 28 | 29 | if (typeof value === "string") { 30 | // Support comma-separated values 31 | value = value.split(/ *, */); 32 | } 33 | 34 | return compile(value || []); 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/cookies.ts: -------------------------------------------------------------------------------- 1 | import type { Cookie } from "../../deps.ts"; 2 | 3 | /** 4 | * Returns `true` if the input has type `{ name: string }`. 5 | * @param value 6 | * @see https://doc.deno.land/https/deno.land/std/http/mod.ts#Cookie 7 | */ 8 | export function hasCookieNameProperty( 9 | value: any, 10 | ): value is Pick { 11 | return value && typeof value === "object" && typeof value.name === "string"; 12 | } 13 | 14 | /** 15 | * Returns `true` if input has all required properties of `Cookie`. 16 | * @param value 17 | * @see https://doc.deno.land/https/deno.land/std/http/mod.ts#Cookie 18 | */ 19 | export function hasCookieRequiredProperties( 20 | value: any, 21 | ): value is Pick { 22 | return hasCookieNameProperty(value) && 23 | typeof (value as any).value === "string"; 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/defineGetter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper function for creating a getter on an object. 3 | * 4 | * @param {object} obj 5 | * @param {string} name 6 | * @param {Function} getter 7 | * @private 8 | */ 9 | export function defineGetter(obj: object, name: string, getter: () => any) { 10 | Object.defineProperty(obj, name, { 11 | configurable: true, 12 | enumerable: true, 13 | get: getter, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/forwarded.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Port of forwarded (https://github.com/jshttp/forwarded/tree/v0.1.2) for Deno. 3 | * 4 | * Licensed as follows: 5 | * 6 | * The MIT License 7 | * 8 | * Copyright (c) 2014-2017 Douglas Christopher Wilson 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining 11 | * a copy of this software and associated documentation files (the 12 | * 'Software'), to deal in the Software without restriction, including 13 | * without limitation the rights to use, copy, modify, merge, publish, 14 | * distribute, sublicense, and/or sell copies of the Software, and to 15 | * permit persons to whom the Software is furnished to do so, subject to 16 | * the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be 19 | * included in all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 22 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | */ 29 | 30 | import type { OpineRequest } from "../types.ts"; 31 | 32 | /** 33 | * Get all addresses in the request, using the `X-Forwarded-For` header. 34 | * 35 | * @param {object} req 36 | * @return {array} 37 | * @public 38 | */ 39 | export function forwarded(req: OpineRequest) { 40 | if (!req) { 41 | throw new TypeError("argument req is required"); 42 | } 43 | 44 | // simple header parsing 45 | const proxyAddrs = parse(req.headers.get("x-forwarded-for") ?? ""); 46 | const { hostname: socketAddr } = req.conn.remoteAddr as Deno.NetAddr; 47 | const addrs = [socketAddr].concat(proxyAddrs); 48 | 49 | // return all addresses 50 | return addrs; 51 | } 52 | 53 | /** 54 | * Parse the X-Forwarded-For header. 55 | * 56 | * @param {string} header 57 | * @private 58 | */ 59 | function parse(header: string) { 60 | const list = []; 61 | let start = header.length; 62 | let end = header.length; 63 | 64 | // gather addresses, backwards 65 | for (let i = header.length - 1; i >= 0; i--) { 66 | switch (header.charCodeAt(i)) { 67 | case 0x20: /* */ 68 | if (start === end) { 69 | start = end = i; 70 | } 71 | 72 | break; 73 | case 0x2c: /* , */ 74 | if (start !== end) { 75 | list.push(header.substring(start, end)); 76 | } 77 | start = end = i; 78 | 79 | break; 80 | default: 81 | start = i; 82 | 83 | break; 84 | } 85 | } 86 | 87 | // final address 88 | if (start !== end) { 89 | list.push(header.substring(start, end)); 90 | } 91 | 92 | return list; 93 | } 94 | -------------------------------------------------------------------------------- /src/utils/merge.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Merge object b with object a. 3 | * 4 | * @param {object} a 5 | * @param {object} b 6 | * @return {object} 7 | * @public 8 | */ 9 | export function merge(a: any, b: any): any { 10 | if (a && b) { 11 | for (let key in b) { 12 | a[key] = b[key]; 13 | } 14 | } 15 | 16 | return a; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/mergeDescriptors.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Port of merge-descriptors (https://github.com/component/merge-descriptors) for Deno. 3 | * 4 | * Licensed as follows: 5 | * 6 | * (The MIT License) 7 | * 8 | * Copyright (c) 2013 Jonathan Ong 9 | * Copyright (c) 2015 Douglas Christopher Wilson 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining 12 | * a copy of this software and associated documentation files (the 13 | * 'Software'), to deal in the Software without restriction, including 14 | * without limitation the rights to use, copy, modify, merge, publish, 15 | * distribute, sublicense, and/or sell copies of the Software, and to 16 | * permit persons to whom the Software is furnished to do so, subject to 17 | * the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be 20 | * included in all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 23 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 25 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 26 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 27 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 28 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | * 30 | */ 31 | 32 | const hasOwnProperty = Object.prototype.hasOwnProperty; 33 | 34 | /** 35 | * Merge the property descriptors of `src` into `dest` 36 | * 37 | * @param {object} dest Object to add descriptors to 38 | * @param {object} src Object to clone descriptors from 39 | * @param {boolean} [redefine=true] Redefine `dest` properties with `src` properties 40 | * @returns {object} Reference to dest 41 | * @public 42 | */ 43 | export function mergeDescriptors( 44 | dest: object, 45 | src: object, 46 | redefine: boolean = true, 47 | ) { 48 | Object.getOwnPropertyNames(src).forEach( 49 | function forEachOwnPropertyName(name) { 50 | if (!redefine && hasOwnProperty.call(dest, name)) { 51 | return; 52 | } 53 | 54 | const descriptor = Object.getOwnPropertyDescriptor(src, name); 55 | Object.defineProperty(dest, name, descriptor as PropertyDescriptor); 56 | }, 57 | ); 58 | 59 | return dest; 60 | } 61 | -------------------------------------------------------------------------------- /src/utils/normalizeType.ts: -------------------------------------------------------------------------------- 1 | import { lookup } from "../../deps.ts"; 2 | 3 | /** 4 | * Parse accept params `str` returning an 5 | * object with `.value`, `.quality` and `.params`. 6 | * 7 | * @param {string} str 8 | * @return {any} 9 | * @private 10 | */ 11 | function acceptParams(str: string) { 12 | const parts = str.split(/ *; */); 13 | const ret = { value: parts[0], quality: 1, params: {} as any }; 14 | 15 | for (let i = 1; i < parts.length; ++i) { 16 | const pms = parts[i].split(/ *= */); 17 | 18 | if ("q" === pms[0]) { 19 | ret.quality = parseFloat(pms[1]); 20 | } else { 21 | ret.params[pms[0]] = pms[1]; 22 | } 23 | } 24 | 25 | return ret; 26 | } 27 | 28 | /** 29 | * Normalize the given `type`, for example "html" becomes "text/html". 30 | * 31 | * @param {string} type 32 | * @return {any} 33 | * @private 34 | */ 35 | export const normalizeType = function (type: string): any { 36 | return ~type.indexOf("/") 37 | ? acceptParams(type) 38 | : { value: lookup(type), params: {} }; 39 | }; 40 | 41 | /** 42 | * Normalize `types`, for example "html" becomes "text/html". 43 | * 44 | * @param {string[]} types 45 | * @return {any[]} 46 | * @private 47 | */ 48 | export const normalizeTypes = function (types: string[]): any[] { 49 | const ret = []; 50 | 51 | for (let i = 0; i < types.length; ++i) { 52 | ret.push(normalizeType(types[i])); 53 | } 54 | 55 | return ret; 56 | }; 57 | -------------------------------------------------------------------------------- /src/utils/stringify.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Stringify JSON, like JSON.stringify, but v8 optimized, with the 3 | * ability to escape characters that can trigger HTML sniffing. 4 | * 5 | * @param {any} value 6 | * @param {Function} replaces 7 | * @param {number} spaces 8 | * @param {boolean} escape 9 | * @returns {string} 10 | * @public 11 | */ 12 | export function stringify( 13 | value: any, 14 | replacer: any, 15 | spaces: number, 16 | escape: boolean, 17 | ): string { 18 | // v8 checks arguments.length for optimizing simple call 19 | // https://bugs.chromium.org/p/v8/issues/detail?id=4730 20 | let json = replacer || spaces 21 | ? JSON.stringify(value, replacer, spaces) 22 | : JSON.stringify(value); 23 | 24 | if (escape) { 25 | json = json.replace(/[<>&]/g, function (c) { 26 | switch (c.charCodeAt(0)) { 27 | case 0x3c: 28 | return "\\u003c"; 29 | case 0x3e: 30 | return "\\u003e"; 31 | case 0x26: 32 | return "\\u0026"; 33 | /* istanbul ignore next: unreachable default */ 34 | default: 35 | return c; 36 | } 37 | }); 38 | } 39 | 40 | return json; 41 | } 42 | -------------------------------------------------------------------------------- /test/deps.ts: -------------------------------------------------------------------------------- 1 | export { deferred } from "https://deno.land/std@0.183.0/async/deferred.ts"; 2 | export type { Deferred } from "https://deno.land/std@0.183.0/async/deferred.ts"; 3 | export { Buffer } from "https://deno.land/std@0.183.0/io/buffer.ts"; 4 | export { expect, mock } from "https://deno.land/x/expect@v0.3.0/mod.ts"; 5 | export { superdeno } from "https://deno.land/x/superdeno@4.8.0/mod.ts"; 6 | export type { 7 | IRequest as SuperDenoRequest, 8 | IResponse as SuperDenoResponse, 9 | } from "https://deno.land/x/superdeno@4.8.0/mod.ts"; 10 | -------------------------------------------------------------------------------- /test/fixtures/% of dogs.txt: -------------------------------------------------------------------------------- 1 | 20% -------------------------------------------------------------------------------- /test/fixtures/.hidden: -------------------------------------------------------------------------------- 1 | I am hidden -------------------------------------------------------------------------------- /test/fixtures/.hidden.txt: -------------------------------------------------------------------------------- 1 | secret -------------------------------------------------------------------------------- /test/fixtures/.mine/name.txt: -------------------------------------------------------------------------------- 1 | deno -------------------------------------------------------------------------------- /test/fixtures/.name: -------------------------------------------------------------------------------- 1 | Deno -------------------------------------------------------------------------------- /test/fixtures/blog/index.html: -------------------------------------------------------------------------------- 1 | index -------------------------------------------------------------------------------- /test/fixtures/blog/post/index.tmpl: -------------------------------------------------------------------------------- 1 |

Blog Post

-------------------------------------------------------------------------------- /test/fixtures/default_layout/name.tmpl: -------------------------------------------------------------------------------- 1 |

$name

-------------------------------------------------------------------------------- /test/fixtures/default_layout/user.tmpl: -------------------------------------------------------------------------------- 1 |

$user.name

-------------------------------------------------------------------------------- /test/fixtures/deno.html: -------------------------------------------------------------------------------- 1 |

deno

-------------------------------------------------------------------------------- /test/fixtures/dinos/names.txt: -------------------------------------------------------------------------------- 1 | deno,superdeno -------------------------------------------------------------------------------- /test/fixtures/do..ts.txt: -------------------------------------------------------------------------------- 1 | ... -------------------------------------------------------------------------------- /test/fixtures/email.tmpl: -------------------------------------------------------------------------------- 1 |

This is an email

-------------------------------------------------------------------------------- /test/fixtures/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmorten/opine/23a24f4334c65a1d6042c2c59a627f3f2548c026/test/fixtures/empty.txt -------------------------------------------------------------------------------- /test/fixtures/foo bar: -------------------------------------------------------------------------------- 1 | baz -------------------------------------------------------------------------------- /test/fixtures/local_layout/user.tmpl: -------------------------------------------------------------------------------- 1 | $user.name -------------------------------------------------------------------------------- /test/fixtures/name.html: -------------------------------------------------------------------------------- 1 |

deno

-------------------------------------------------------------------------------- /test/fixtures/name.tmpl: -------------------------------------------------------------------------------- 1 |

$name

-------------------------------------------------------------------------------- /test/fixtures/name.txt: -------------------------------------------------------------------------------- 1 | deno -------------------------------------------------------------------------------- /test/fixtures/nums.txt: -------------------------------------------------------------------------------- 1 | 123456789 -------------------------------------------------------------------------------- /test/fixtures/pets/index.html: -------------------------------------------------------------------------------- 1 | smudge 2 | tilly 3 | georgie -------------------------------------------------------------------------------- /test/fixtures/some thing.txt: -------------------------------------------------------------------------------- 1 | hey -------------------------------------------------------------------------------- /test/fixtures/todo.html: -------------------------------------------------------------------------------- 1 |
  • groceries
  • -------------------------------------------------------------------------------- /test/fixtures/todo.txt: -------------------------------------------------------------------------------- 1 | - groceries -------------------------------------------------------------------------------- /test/fixtures/user.html: -------------------------------------------------------------------------------- 1 |

    {{user.name}}

    -------------------------------------------------------------------------------- /test/fixtures/user.tmpl: -------------------------------------------------------------------------------- 1 |

    $user.name

    -------------------------------------------------------------------------------- /test/fixtures/users/deno.txt: -------------------------------------------------------------------------------- 1 | dino -------------------------------------------------------------------------------- /test/fixtures/users/index.html: -------------------------------------------------------------------------------- 1 |

    deno, superdeno

    -------------------------------------------------------------------------------- /test/support/tmpl.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | const variableRegExp = /\$([0-9a-zA-Z\.]+)/g; 3 | 4 | function generateVariableLookup(data: any) { 5 | return function variableLookup(str: string, path: string) { 6 | const parts = path.split("."); 7 | let value = data; 8 | 9 | for (let i = 0; i < parts.length; i++) { 10 | value = value[parts[i]]; 11 | } 12 | 13 | return value; 14 | }; 15 | } 16 | 17 | export async function tmpl( 18 | fileName: string, 19 | options: any, 20 | ) { 21 | const str = await Deno.readTextFile(fileName); 22 | 23 | try { 24 | return str.replace(variableRegExp, generateVariableLookup(options)); 25 | } catch (e) { 26 | e.name = "RenderError"; 27 | throw e; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/units/app.all.test.ts: -------------------------------------------------------------------------------- 1 | import opine from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("app.all()", function () { 6 | it("should add a router per method", function (done) { 7 | const app = opine(); 8 | 9 | app.all("/deno", function (req, res) { 10 | res.end(req.method); 11 | }); 12 | 13 | superdeno(app) 14 | .put("/deno") 15 | .expect("PUT", function () { 16 | superdeno(app) 17 | .get("/deno") 18 | .expect("GET", done); 19 | }); 20 | }); 21 | 22 | it("should run the callback for a method just once", function (done) { 23 | const app = opine(); 24 | let n = 0; 25 | 26 | app.all("/*", function (_req, _res, next) { 27 | if (n++) return done(new Error("DELETE called several times")); 28 | next(); 29 | }); 30 | 31 | superdeno(app) 32 | .delete("/deno") 33 | .expect(404, done); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/units/app.delete.test.ts: -------------------------------------------------------------------------------- 1 | import opine from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("app.delete()", function () { 6 | it("should alias app.delete()", function (done) { 7 | const app = opine(); 8 | 9 | app.delete("/deno", function (_req, res) { 10 | res.end("deleted deno!"); 11 | }); 12 | 13 | superdeno(app) 14 | .delete("/deno") 15 | .expect("deleted deno!", done); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/units/app.engine.test.ts: -------------------------------------------------------------------------------- 1 | import opine from "../../mod.ts"; 2 | import { describe, it } from "../utils.ts"; 3 | import { expect } from "../deps.ts"; 4 | import { dirname, join } from "../../deps.ts"; 5 | 6 | const __dirname = dirname(import.meta.url); 7 | 8 | async function render(path: string, options: any) { 9 | const str = await Deno.readTextFile(path); 10 | 11 | return str.replace("{{user.name}}", options.user.name); 12 | } 13 | 14 | describe("app", function () { 15 | describe(".engine(ext, fn)", function () { 16 | it("should map a template engine", function (done) { 17 | const app = opine(); 18 | 19 | app.set("views", join(__dirname, "../fixtures")); 20 | app.engine(".html", render); 21 | app.locals.user = { name: "Deno" }; 22 | 23 | app.render("user.html", function (err: any, str: string) { 24 | if (err) { 25 | return done(err); 26 | } 27 | 28 | expect(str).toEqual("

    Deno

    "); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('should work without leading "."', function (done) { 34 | const app = opine(); 35 | 36 | app.set("views", join(__dirname, "../fixtures")); 37 | app.engine("html", render); 38 | app.locals.user = { name: "Deno" }; 39 | 40 | app.render("user.html", function (err: any, str: string) { 41 | if (err) { 42 | return done(err); 43 | } 44 | 45 | expect(str).toEqual("

    Deno

    "); 46 | done(); 47 | }); 48 | }); 49 | 50 | it('should work "view engine" setting', function (done) { 51 | const app = opine(); 52 | 53 | app.set("views", join(__dirname, "../fixtures")); 54 | app.engine("html", render); 55 | app.set("view engine", "html"); 56 | app.locals.user = { name: "Deno" }; 57 | 58 | app.render("user.html", function (err: any, str: string) { 59 | if (err) { 60 | return done(err); 61 | } 62 | 63 | expect(str).toEqual("

    Deno

    "); 64 | done(); 65 | }); 66 | }); 67 | 68 | it('should work "view engine" with leading "."', function (done) { 69 | const app = opine(); 70 | 71 | app.set("views", join(__dirname, "../fixtures")); 72 | app.engine(".html", render); 73 | app.set("view engine", ".html"); 74 | app.locals.user = { name: "Deno" }; 75 | 76 | app.render("user.html", function (err: any, str: string) { 77 | if (err) { 78 | return done(err); 79 | } 80 | 81 | expect(str).toEqual("

    Deno

    "); 82 | done(); 83 | }); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/units/app.head.test.ts: -------------------------------------------------------------------------------- 1 | import opine from "../../mod.ts"; 2 | import { expect, superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("HEAD", function () { 6 | it("should default to GET", function (done) { 7 | const app = opine(); 8 | 9 | app.get("/deno", function (_req, res) { 10 | res.send("deno"); 11 | }); 12 | 13 | superdeno(app) 14 | .head("/deno") 15 | .expect(200, done); 16 | }); 17 | 18 | it("should output the same headers as GET requests", function (done) { 19 | const app = opine(); 20 | 21 | app.get("/deno", function (_req, res) { 22 | // send() detects HEAD 23 | res.send("deno"); 24 | }); 25 | 26 | superdeno(app) 27 | .get("/deno") 28 | .expect(200, function (err, res) { 29 | if (err) { 30 | return done(err); 31 | } 32 | 33 | const headers = res.header; 34 | 35 | superdeno(app) 36 | .get("/deno") 37 | .expect(200, function (err, res) { 38 | if (err) { 39 | return done(err); 40 | } 41 | 42 | delete headers.date; 43 | delete res.header.date; 44 | expect(res.header).toEqual(headers); 45 | done(); 46 | }); 47 | }); 48 | }); 49 | }); 50 | 51 | describe("app.head()", function () { 52 | it("should override", function (done) { 53 | const app = opine(); 54 | let called = false; 55 | 56 | app.head("/deno", function (_req, res) { 57 | called = true; 58 | res.end(""); 59 | }); 60 | 61 | app.get("/deno", function () { 62 | throw new Error("should not be called"); 63 | }); 64 | 65 | superdeno(app) 66 | .head("/deno") 67 | .expect(200, function () { 68 | expect(called).toBeTruthy(); 69 | done(); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/units/app.listen.test.ts: -------------------------------------------------------------------------------- 1 | import opine from "../../mod.ts"; 2 | import { describe, it } from "../utils.ts"; 3 | 4 | describe("app.listen()", () => { 5 | describe("when passing a port", () => { 6 | it("should wrap with an HTTP server", () => { 7 | const app = opine(); 8 | 9 | app.get("/deno", function (_req, res) { 10 | res.end("Hello Deno!"); 11 | }); 12 | 13 | const server = app.listen(9999); 14 | server.close(); 15 | }); 16 | }); 17 | 18 | describe("when passing an address", () => { 19 | it("should wrap with an HTTP server", () => { 20 | const app = opine(); 21 | 22 | app.get("/deno", function (_req, res) { 23 | res.end("Hello Deno!"); 24 | }); 25 | 26 | const server = app.listen("localhost:9999"); 27 | server.close(); 28 | }); 29 | }); 30 | 31 | describe("when passing an object", () => { 32 | it("should wrap with an HTTP server", () => { 33 | const app = opine(); 34 | 35 | app.get("/deno", function (_req, res) { 36 | res.end("Hello Deno!"); 37 | }); 38 | 39 | const server = app.listen({ port: 9999 }); 40 | server.close(); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/units/app.locals.test.ts: -------------------------------------------------------------------------------- 1 | import opine from "../../mod.ts"; 2 | import { expect } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | const mockUser = Symbol("test-user"); 6 | const mockAge = Symbol("test-age"); 7 | const mockSetting = "test-setting"; 8 | const mockValue = Symbol("test-value"); 9 | 10 | describe("app", () => { 11 | describe(".locals(obj)", () => { 12 | it("should merge locals", () => { 13 | const app = opine(); 14 | 15 | expect(Object.keys(app.locals)).toEqual(["settings"]); 16 | 17 | app.locals.user = mockUser; 18 | app.locals.age = mockAge; 19 | 20 | expect(Object.keys(app.locals)).toEqual(["settings", "user", "age"]); 21 | expect(app.locals.user).toEqual(mockUser); 22 | expect(app.locals.age).toEqual(mockAge); 23 | }); 24 | }); 25 | 26 | describe(".locals.settings", () => { 27 | it("should expose app settings", () => { 28 | const app = opine(); 29 | app.set(mockSetting, mockValue); 30 | 31 | const obj = app.locals.settings; 32 | expect(obj).toHaveProperty(mockSetting); 33 | expect(obj[mockSetting]).toEqual(mockValue); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/units/app.request.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | import { parseUrl } from "../../src/utils/parseUrl.ts"; 5 | 6 | describe("app", function () { 7 | describe(".request", function () { 8 | it("should extend the request prototype", function (done) { 9 | const app = opine(); 10 | 11 | (app.request as any).querystring = function () { 12 | return (parseUrl({ url: this.url } as any) as any).search; 13 | }; 14 | 15 | app.use(function (req, res) { 16 | res.end((req as any).querystring()); 17 | }); 18 | 19 | superdeno(app) 20 | .get("/foo?name=Deno") 21 | .expect("?name=Deno", done); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/units/app.response.test.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | import { opine } from "../../mod.ts"; 3 | import { superdeno } from "../deps.ts"; 4 | import { describe, it } from "../utils.ts"; 5 | 6 | describe("app", function () { 7 | describe(".response", function () { 8 | it("should extend the response prototype", function (done) { 9 | const app = opine(); 10 | 11 | (app.response as any).shout = function (str: string) { 12 | this.send(str.toUpperCase()); 13 | }; 14 | 15 | app.use(function (_req, res) { 16 | (res as any).shout("hey"); 17 | }); 18 | 19 | superdeno(app) 20 | .get("/") 21 | .expect("HEY", done); 22 | }); 23 | 24 | it("should not be influenced by other app protos", function (done) { 25 | const app = opine(); 26 | const app2 = opine(); 27 | 28 | (app.response as any).shout = function (str: string) { 29 | this.send(str.toUpperCase()); 30 | }; 31 | 32 | (app2.response as any).shout = function (str: string) { 33 | this.send(str); 34 | }; 35 | 36 | app.use(function (_req, res) { 37 | (res as any).shout("hey"); 38 | }); 39 | 40 | superdeno(app) 41 | .get("/") 42 | .expect("HEY", done); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/units/app.route.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("app.route", function () { 6 | it("should return a new route", function (done) { 7 | const app = opine(); 8 | 9 | app.route("/foo") 10 | .get(function (req, res) { 11 | res.send("get"); 12 | }) 13 | .post(function (req, res) { 14 | res.send("post"); 15 | }); 16 | 17 | superdeno(app) 18 | .post("/foo") 19 | .expect("post", done); 20 | }); 21 | 22 | it("should all .VERB after .all", function (done) { 23 | const app = opine(); 24 | 25 | app.route("/foo") 26 | .all(function (req, res, next) { 27 | next(); 28 | }) 29 | .get(function (req, res) { 30 | res.send("get"); 31 | }) 32 | .post(function (req, res) { 33 | res.send("post"); 34 | }); 35 | 36 | superdeno(app) 37 | .post("/foo") 38 | .expect("post", done); 39 | }); 40 | 41 | it("should support dynamic routes", function (done) { 42 | const app = opine(); 43 | 44 | app.route("/:foo") 45 | .get(function (req, res) { 46 | res.send(req.params.foo); 47 | }); 48 | 49 | superdeno(app) 50 | .get("/test") 51 | .expect("test", done); 52 | }); 53 | 54 | it("should not error on empty routes", function (done) { 55 | const app = opine(); 56 | 57 | app.route("/:foo"); 58 | 59 | superdeno(app) 60 | .get("/test") 61 | .expect(404, done); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/units/app.routes.error.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { expect, superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | import type { 5 | NextFunction, 6 | OpineRequest, 7 | OpineResponse, 8 | } from "../../src/types.ts"; 9 | 10 | describe("app", function () { 11 | describe(".VERB()", function () { 12 | it("should not get invoked without error handler on error", function ( 13 | done, 14 | ) { 15 | const app = opine(); 16 | 17 | app.use(function (req, res, next) { 18 | next(new Error("boom!")); 19 | }); 20 | 21 | app.get("/bar", function (req, res) { 22 | res.send("hello, world!"); 23 | }); 24 | 25 | superdeno(app) 26 | .post("/bar") 27 | .expect(500, /Error: boom!/, done); 28 | }); 29 | 30 | it( 31 | "should only call an error handling routing callback when an error is propagated", 32 | function ( 33 | done, 34 | ) { 35 | const app = opine(); 36 | 37 | let a = false; 38 | let b = false; 39 | let c = false; 40 | let d = false; 41 | 42 | app.get( 43 | "/", 44 | function (req, res, next) { 45 | next(new Error("fabricated error")); 46 | }, 47 | function (req, res, next) { 48 | a = true; 49 | next(); 50 | }, 51 | function ( 52 | err: any, 53 | req: OpineRequest, 54 | res: OpineResponse, 55 | next: NextFunction, 56 | ) { 57 | b = true; 58 | expect(err.message).toEqual("fabricated error"); 59 | next(err); 60 | }, 61 | function ( 62 | err: any, 63 | req: OpineRequest, 64 | res: OpineResponse, 65 | next: NextFunction, 66 | ) { 67 | c = true; 68 | expect(err.message).toEqual("fabricated error"); 69 | next(); 70 | }, 71 | function ( 72 | err: any, 73 | req: OpineRequest, 74 | res: OpineResponse, 75 | next: NextFunction, 76 | ) { 77 | d = true; 78 | next(); 79 | }, 80 | function (req, res) { 81 | expect(a).toBe(false); 82 | expect(b).toBe(true); 83 | expect(c).toBe(true); 84 | expect(d).toBe(false); 85 | res.sendStatus(204); 86 | }, 87 | ); 88 | 89 | superdeno(app) 90 | .get("/") 91 | .expect(204, done); 92 | }, 93 | ); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /test/units/app.test.ts: -------------------------------------------------------------------------------- 1 | import opine from "../../mod.ts"; 2 | import { expect, superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("app", () => { 6 | it("should inherit from event emitter", async (done) => { 7 | const mockEvent = "test-event"; 8 | const mockArg = Symbol("test-arg"); 9 | 10 | const app = opine(); 11 | app.on(mockEvent, (arg) => { 12 | expect(arg).toBe(mockArg); 13 | done(); 14 | }); 15 | app.emit(mockEvent, mockArg); 16 | }); 17 | 18 | it("should be callable", () => { 19 | const app = opine(); 20 | expect(typeof app).toEqual("function"); 21 | }); 22 | 23 | it("should 404 without routes", (done) => { 24 | const app = opine(); 25 | superdeno(app).get("/").expect(404, done); 26 | }); 27 | }); 28 | 29 | describe("app.parent", () => { 30 | it("should return the parent when mounted", () => { 31 | const app = opine(); 32 | const blog = opine(); 33 | const blogAdmin = opine(); 34 | 35 | app.use("/blog", blog); 36 | blog.use("/admin", blogAdmin); 37 | 38 | expect(app.parent).toBeUndefined(); 39 | expect(blog.parent).toEqual(app); 40 | expect(blogAdmin.parent).toEqual(blog); 41 | }); 42 | }); 43 | 44 | describe("app.mountpath", () => { 45 | it("should return the mounted path", () => { 46 | const admin = opine(); 47 | const app = opine(); 48 | const blog = opine(); 49 | const fallback = opine(); 50 | 51 | app.use("/blog", blog); 52 | app.use(fallback); 53 | blog.use("/admin", admin); 54 | 55 | expect(admin.mountpath).toEqual("/admin"); 56 | expect(app.mountpath).toEqual("/"); 57 | expect(blog.mountpath).toEqual("/blog"); 58 | expect(fallback.mountpath).toEqual("/"); 59 | }); 60 | }); 61 | 62 | describe("app.path()", () => { 63 | it("should return the canonical", () => { 64 | const app = opine(); 65 | const blog = opine(); 66 | const blogAdmin = opine(); 67 | 68 | app.use("/blog", blog); 69 | blog.use("/admin", blogAdmin); 70 | 71 | expect(app.path()).toEqual(""); 72 | expect(blog.path()).toEqual("/blog"); 73 | expect(blogAdmin.path()).toEqual("/blog/admin"); 74 | }); 75 | }); 76 | 77 | describe("view cache", () => { 78 | it('should enable "view cache" by default', function () { 79 | const app = opine(); 80 | expect(app.enabled("view cache")).toBeTruthy(); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/units/bodyParser.json.test.ts: -------------------------------------------------------------------------------- 1 | import { json } from "../../mod.ts"; 2 | import { Buffer, expect } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | const encoder = new TextEncoder(); 6 | const mockJson = { hello: "deno" }; 7 | 8 | const jsonHeaders = new Headers(); 9 | jsonHeaders.set("Content-Type", "application/json"); 10 | 11 | const textHeaders = new Headers(); 12 | textHeaders.set("Content-Type", "text/plain"); 13 | textHeaders.set("Content-Length", "1"); 14 | 15 | describe("bodyParser: json", () => { 16 | it("should handle requests without bodies", (done) => { 17 | const req: any = { headers: jsonHeaders }; 18 | const parser = json(); 19 | 20 | parser(req, {} as any, (err?: any) => { 21 | if (err) throw err; 22 | expect(req.parsedBody).toEqual({}); 23 | done(); 24 | }); 25 | }); 26 | 27 | it("should handle requests with encoded JSON bodies", (done) => { 28 | const req: any = { 29 | body: new Buffer(encoder.encode(JSON.stringify(mockJson))), 30 | headers: jsonHeaders, 31 | }; 32 | req.headers.set("Content-Length", "1"); 33 | const parser = json(); 34 | 35 | parser(req, {} as any, (err?: any) => { 36 | if (err) throw err; 37 | expect(req.parsedBody).toEqual(mockJson); 38 | done(); 39 | }); 40 | }); 41 | 42 | it("should handle requests with encoded JSON array bodies", (done) => { 43 | const req: any = { 44 | body: new Buffer(encoder.encode(JSON.stringify([mockJson]))), 45 | headers: jsonHeaders, 46 | }; 47 | req.headers.set("Content-Length", "1"); 48 | const parser = json(); 49 | 50 | parser(req, {} as any, (err?: any) => { 51 | if (err) throw err; 52 | expect(req.parsedBody).toEqual([mockJson]); 53 | done(); 54 | }); 55 | }); 56 | 57 | it("should handle requests with encoded JSON bodies containing whitespace", (done) => { 58 | const req: any = { 59 | body: new Buffer( 60 | encoder.encode(` \r\n\t\n${JSON.stringify(mockJson)}\n\t\r\n `), 61 | ), 62 | headers: jsonHeaders, 63 | }; 64 | req.headers.set("Content-Length", "1"); 65 | const parser = json(); 66 | 67 | parser(req, {} as any, (err?: any) => { 68 | if (err) throw err; 69 | expect(req.parsedBody).toEqual(mockJson); 70 | done(); 71 | }); 72 | }); 73 | 74 | it("should not alter request bodies when the content type is not JSON", (done) => { 75 | const mockBody = Symbol("test-body"); 76 | const req: any = { 77 | body: mockBody, 78 | headers: textHeaders, 79 | }; 80 | req.headers.set("Content-Length", "1"); 81 | const parser = json(); 82 | 83 | parser(req, {} as any, (err?: unknown) => { 84 | if (err) throw err; 85 | expect(req.body).toEqual(mockBody); 86 | expect(req.parsedBody).toBeUndefined(); 87 | done(); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/units/bodyParser.raw.test.ts: -------------------------------------------------------------------------------- 1 | import { raw } from "../../mod.ts"; 2 | import { Buffer, expect } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | const encoder = new TextEncoder(); 6 | const mockText = "Hello Deno!"; 7 | 8 | const octetHeaders = new Headers(); 9 | octetHeaders.set("Content-Type", "application/octet-stream"); 10 | 11 | const textHeaders = new Headers(); 12 | textHeaders.set("Content-Type", "text/plain"); 13 | textHeaders.set("Content-Length", "1"); 14 | 15 | describe("bodyParser: raw", () => { 16 | it("should handle requests without bodies", (done) => { 17 | const req: any = { headers: octetHeaders }; 18 | const parser = raw(); 19 | 20 | parser(req, {} as any, (err?: any) => { 21 | if (err) throw err; 22 | expect(req.parsedBody).toEqual(""); 23 | done(); 24 | }); 25 | }); 26 | 27 | it("should handle requests with encoded raw bodies", (done) => { 28 | const encodedText = encoder.encode(mockText); 29 | const req: any = { 30 | body: new Buffer(encodedText), 31 | headers: octetHeaders, 32 | }; 33 | req.headers.set("Content-Length", "1"); 34 | const parser = raw(); 35 | 36 | parser(req, {} as any, (err?: any) => { 37 | if (err) throw err; 38 | expect(req.parsedBody).toEqual(encodedText); 39 | done(); 40 | }); 41 | }); 42 | 43 | it("should not alter request bodies when the content type is not raw", (done) => { 44 | const mockBody = Symbol("test-body"); 45 | const req: any = { 46 | body: mockBody, 47 | headers: textHeaders, 48 | }; 49 | req.headers.set("Content-Length", "1"); 50 | const parser = raw(); 51 | 52 | parser(req, {} as any, (err?: any) => { 53 | if (err) throw err; 54 | expect(req.body).toEqual(mockBody); 55 | expect(req.parsedBody).toBeUndefined(); 56 | done(); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/units/bodyParser.text.test.ts: -------------------------------------------------------------------------------- 1 | import { text } from "../../mod.ts"; 2 | import { Buffer, expect } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | const encoder = new TextEncoder(); 6 | const mockText = "Hello Deno!"; 7 | 8 | const jsonHeaders = new Headers(); 9 | jsonHeaders.set("Content-Type", "application/json"); 10 | jsonHeaders.set("Content-Length", "1"); 11 | 12 | const textHeaders = new Headers(); 13 | textHeaders.set("Content-Type", "text/plain"); 14 | 15 | describe("bodyParser: text", () => { 16 | it("should handle requests without bodies", (done) => { 17 | const req: any = { headers: textHeaders }; 18 | req.headers.set("Content-Length", "0"); 19 | const parser = text(); 20 | 21 | parser(req, {} as any, (err?: any) => { 22 | if (err) throw err; 23 | expect(req.parsedBody).toEqual(""); 24 | done(); 25 | }); 26 | }); 27 | 28 | it("should handle requests with encoded text bodies", (done) => { 29 | const req: any = { 30 | body: new Buffer(encoder.encode(mockText)), 31 | headers: textHeaders, 32 | }; 33 | req.headers.set("Content-Length", "1"); 34 | const parser = text(); 35 | 36 | parser(req, {} as any, (err?: any) => { 37 | if (err) throw err; 38 | expect(req.parsedBody).toEqual(mockText); 39 | done(); 40 | }); 41 | }); 42 | 43 | it("should not alter request bodies when the content type is not text", (done) => { 44 | const mockBody = Symbol("test-body"); 45 | const req: any = { 46 | body: mockBody, 47 | headers: jsonHeaders, 48 | }; 49 | const parser = text(); 50 | 51 | parser(req, {} as any, (err?: any) => { 52 | if (err) throw err; 53 | expect(req.body).toEqual(mockBody); 54 | expect(req.parsedBody).toBeUndefined(); 55 | done(); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/units/bodyParser.urlencoded.test.ts: -------------------------------------------------------------------------------- 1 | import { urlencoded } from "../../mod.ts"; 2 | import { Buffer, expect } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | const encoder = new TextEncoder(); 6 | const mockFormData = new URLSearchParams("hello=deno&opine=is+awesome"); 7 | 8 | const formHeaders = new Headers(); 9 | formHeaders.set("Content-Type", "application/x-www-form-urlencoded"); 10 | 11 | const textHeaders = new Headers(); 12 | textHeaders.set("Content-Type", "text/plain"); 13 | textHeaders.set("Content-Length", "1"); 14 | 15 | describe("bodyParser: urlencoded", () => { 16 | it("should handle requests without bodies", (done) => { 17 | const req: any = { headers: formHeaders }; 18 | const parser = urlencoded(); 19 | 20 | parser(req, {} as any, (err?: any) => { 21 | if (err) throw err; 22 | expect(req.parsedBody).toEqual({}); 23 | done(); 24 | }); 25 | }); 26 | 27 | it("should handle requests with encoded urlencoded bodies", (done) => { 28 | const req: any = { 29 | body: new Buffer(encoder.encode(mockFormData.toString())), 30 | headers: formHeaders, 31 | }; 32 | req.headers.set("Content-Length", "1"); 33 | const parser = urlencoded(); 34 | 35 | parser(req, {} as any, (err?: any) => { 36 | if (err) throw err; 37 | expect(req.parsedBody).toEqual(Object.fromEntries(mockFormData)); 38 | done(); 39 | }); 40 | }); 41 | 42 | it("should not alter request bodies when the content type is not urlencoded", (done) => { 43 | const mockBody = Symbol("test-body"); 44 | const req: any = { 45 | body: mockBody, 46 | headers: textHeaders, 47 | }; 48 | req.headers.set("Content-Length", "1"); 49 | const parser = urlencoded(); 50 | 51 | parser(req, {} as any, (err?: any) => { 52 | if (err) throw err; 53 | expect(req.body).toEqual(mockBody); 54 | expect(req.parsedBody).toBeUndefined(); 55 | done(); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/units/etag.test.ts: -------------------------------------------------------------------------------- 1 | import { etag, wetag } from "../../src/utils/compileETag.ts"; 2 | import { expect } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("etag(body)", function () { 6 | it("should support strings", function () { 7 | expect(etag("opine!")).toEqual('"6-331971ea313f9b3c58b20a2d604"'); 8 | }); 9 | 10 | it("should support utf8 strings", function () { 11 | expect(etag("opine❤")) 12 | .toEqual('"8-9d8d9352c939ef86574bccbd3c4"'); 13 | }); 14 | 15 | it("should support Uint8Array", function () { 16 | const encoder = new TextEncoder(); 17 | expect(etag(encoder.encode("opine!"))) 18 | .toEqual('"6-331971ea313f9b3c58b20a2d604"'); 19 | }); 20 | 21 | it("should support empty string", function () { 22 | expect(etag("")) 23 | .toEqual('"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'); 24 | }); 25 | }); 26 | 27 | describe("wetag(body, encoding)", function () { 28 | it("should support strings", function () { 29 | expect(wetag("opine!")) 30 | .toEqual('W/"6-331971ea313f9b3c58b20a2d604"'); 31 | }); 32 | 33 | it("should support utf8 strings", function () { 34 | expect(wetag("opine❤")) 35 | .toEqual('W/"8-9d8d9352c939ef86574bccbd3c4"'); 36 | }); 37 | 38 | it("should support buffer", function () { 39 | const encoder = new TextEncoder(); 40 | expect(wetag(encoder.encode("opine!"))) 41 | .toEqual('W/"6-331971ea313f9b3c58b20a2d604"'); 42 | }); 43 | 44 | it("should support empty string", function () { 45 | expect(wetag("")) 46 | .toEqual('W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/units/exports.test.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | import { application, opine, request, response, Router } from "../../mod.ts"; 3 | import { expect, superdeno } from "../deps.ts"; 4 | import { describe, it } from "../utils.ts"; 5 | 6 | describe("exports", () => { 7 | it("should expose Router", () => { 8 | expect(Router).toBeInstanceOf(Function); 9 | }); 10 | 11 | it("should expose the application prototype", () => { 12 | expect(application.set).toBeInstanceOf(Function); 13 | }); 14 | 15 | it("should expose the request prototype", () => { 16 | expect(request.get).toBeInstanceOf(Function); 17 | }); 18 | 19 | it("should expose the response prototype", () => { 20 | expect(response.send).toBeInstanceOf(Function); 21 | }); 22 | 23 | it("should permit modifying the .application prototype", () => { 24 | const mockReturnValue = Symbol("test-return-value"); 25 | 26 | (application as any).mockExtensionMethod = () => { 27 | return mockReturnValue; 28 | }; 29 | expect((opine() as any).mockExtensionMethod()).toEqual(mockReturnValue); 30 | }); 31 | 32 | it("should permit modifying the .request prototype", function (done) { 33 | (request as any).foo = function () { 34 | return "bar"; 35 | }; 36 | const app = opine(); 37 | 38 | app.use(function (req, res, next) { 39 | res.end((req as any).foo()); 40 | }); 41 | 42 | superdeno(app) 43 | .get("/") 44 | .expect("bar", done); 45 | }); 46 | 47 | it("should permit modifying the .response prototype", function (done) { 48 | (response as any).foo = function () { 49 | (this as any).send("bar"); 50 | }; 51 | const app = opine(); 52 | 53 | app.use(function (req, res, next) { 54 | (res as any).foo(); 55 | }); 56 | 57 | superdeno(app) 58 | .get("/") 59 | .expect("bar", done); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/units/middleware.basic.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { expect, superdeno } from "../deps.ts"; 3 | import { readAll } from "../../deps.ts"; 4 | import { describe, it } from "../utils.ts"; 5 | 6 | describe("middleware", function () { 7 | describe(".next()", function () { 8 | it("should behave like connect", function (done) { 9 | const app = opine(); 10 | const calls: any[] = []; 11 | 12 | app.use(function (req, res, next) { 13 | calls.push("one"); 14 | next(); 15 | }); 16 | 17 | app.use(function (req, res, next) { 18 | calls.push("two"); 19 | next(); 20 | }); 21 | 22 | app.use(async function (req, res) { 23 | res.set("Content-Type", "application/json; charset=utf-8"); 24 | const raw = await readAll(req.body); 25 | const data = (new TextDecoder()).decode(raw); 26 | res.end(data); 27 | }); 28 | 29 | superdeno(app) 30 | .post("/") 31 | .set("Content-Type", "application/json") 32 | .send('{"foo":"bar"}') 33 | .expect("Content-Type", "application/json; charset=utf-8") 34 | .expect(function () { 35 | expect(calls).toEqual(["one", "two"]); 36 | }) 37 | .expect(200, '{"foo":"bar"}', done); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/units/regression.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("throw after .end()", function () { 6 | it("should fail gracefully", function (done) { 7 | const app = opine(); 8 | 9 | app.get("/", function (_req, res) { 10 | res.end("yay"); 11 | throw new Error("boom"); 12 | }); 13 | 14 | superdeno(app) 15 | .get("/") 16 | .expect("yay") 17 | .expect(200, done); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/units/req.acceptsCharsets.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("req", function () { 6 | describe(".acceptsCharsets(type)", function () { 7 | describe("when Accept-Charset is not present", function () { 8 | it("should return true", function (done) { 9 | const app = opine(); 10 | 11 | app.use(function (req, res, next) { 12 | res.end(req.acceptsCharsets("utf-8") ? "yes" : "no"); 13 | }); 14 | 15 | superdeno(app) 16 | .get("/") 17 | .expect("yes", done); 18 | }); 19 | }); 20 | 21 | describe("when Accept-Charset is present", function () { 22 | it("should return true", function (done) { 23 | const app = opine(); 24 | 25 | app.use(function (req, res, next) { 26 | res.end(req.acceptsCharsets("utf-8") ? "yes" : "no"); 27 | }); 28 | 29 | superdeno(app) 30 | .get("/") 31 | .set("Accept-Charset", "foo, bar, utf-8") 32 | .expect("yes", done); 33 | }); 34 | 35 | it("should return false otherwise", function (done) { 36 | const app = opine(); 37 | 38 | app.use(function (req, res, next) { 39 | res.end(req.acceptsCharsets("utf-8") ? "yes" : "no"); 40 | }); 41 | 42 | superdeno(app) 43 | .get("/") 44 | .set("Accept-Charset", "foo, bar") 45 | .expect("no", done); 46 | }); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/units/req.acceptsEncoding.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { expect, superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("req", function () { 6 | describe(".acceptsEncodings", function () { 7 | it("should be true if encoding accepted", function (done) { 8 | const app = opine(); 9 | 10 | app.use(function (req, res) { 11 | expect(req.acceptsEncodings("gzip")).toBeTruthy(); 12 | expect(req.acceptsEncodings("deflate")).toBeTruthy(); 13 | res.end(); 14 | }); 15 | 16 | superdeno(app) 17 | .get("/") 18 | .set("Accept-Encoding", "gzip, deflate") 19 | .expect(200, done); 20 | }); 21 | 22 | it("should be false if encoding not accepted", function (done) { 23 | const app = opine(); 24 | 25 | app.use(function (req, res) { 26 | expect(req.acceptsEncodings("bogus")).toBeFalsy(); 27 | res.end(); 28 | }); 29 | 30 | superdeno(app) 31 | .get("/") 32 | .set("Accept-Encoding", "gzip, deflate") 33 | .expect(200, done); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/units/req.acceptsLanguages.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { expect, superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("req", function () { 6 | describe(".acceptsLanguages", function () { 7 | it("should be true if language accepted", function (done) { 8 | const app = opine(); 9 | 10 | app.use(function (req, res) { 11 | expect(req.acceptsLanguages("en-us")).toBeTruthy(); 12 | expect(req.acceptsLanguages("en")).toBeTruthy(); 13 | res.end(); 14 | }); 15 | 16 | superdeno(app) 17 | .get("/") 18 | .set("Accept-Language", "en;q=.5, en-us") 19 | .expect(200, done); 20 | }); 21 | 22 | it("should be false if language not accepted", function (done) { 23 | const app = opine(); 24 | 25 | app.use(function (req, res) { 26 | expect(req.acceptsLanguages("es")).toBeFalsy(); 27 | res.end(); 28 | }); 29 | 30 | superdeno(app) 31 | .get("/") 32 | .set("Accept-Language", "en;q=.5, en-us") 33 | .expect(200, done); 34 | }); 35 | 36 | describe("when Accept-Language is not present", function () { 37 | it("should always return true", function (done) { 38 | const app = opine(); 39 | 40 | app.use(function (req, res) { 41 | expect(req.acceptsLanguages("en")).toBeTruthy(); 42 | expect(req.acceptsLanguages("es")).toBeTruthy(); 43 | expect(req.acceptsLanguages("jp")).toBeTruthy(); 44 | res.end(); 45 | }); 46 | 47 | superdeno(app) 48 | .get("/") 49 | .expect(200, done); 50 | }); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/units/req.baseUrl.test.ts: -------------------------------------------------------------------------------- 1 | import { opine, Router } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("req", function () { 6 | describe(".baseUrl", function () { 7 | it("should be empty for top-level route", function (done) { 8 | const app = opine(); 9 | 10 | app.get("/:a", function (req, res) { 11 | res.end(req.baseUrl); 12 | }); 13 | 14 | superdeno(app) 15 | .get("/foo") 16 | .expect(200, "", done); 17 | }); 18 | 19 | it("should contain lower path", function (done) { 20 | const app = opine(); 21 | const sub = Router(); 22 | 23 | sub.get("/:b", function (req, res) { 24 | res.end(req.baseUrl); 25 | }); 26 | app.use("/:a", sub); 27 | 28 | superdeno(app) 29 | .get("/foo/bar") 30 | .expect(200, "/foo", done); 31 | }); 32 | 33 | it("should contain full lower path", function (done) { 34 | const app = opine(); 35 | const sub1 = Router(); 36 | const sub2 = Router(); 37 | const sub3 = Router(); 38 | 39 | sub3.get("/:d", function (req, res) { 40 | res.end(req.baseUrl); 41 | }); 42 | sub2.use("/:c", sub3); 43 | sub1.use("/:b", sub2); 44 | app.use("/:a", sub1); 45 | 46 | superdeno(app) 47 | .get("/foo/bar/baz/zed") 48 | .expect(200, "/foo/bar/baz", done); 49 | }); 50 | 51 | it("should travel through routers correctly", function (done) { 52 | const urls: any[] = []; 53 | const app = opine(); 54 | const sub1 = Router(); 55 | const sub2 = Router(); 56 | const sub3 = Router(); 57 | 58 | sub3.get("/:d", function (req, _res, next) { 59 | urls.push("0@" + req.baseUrl); 60 | next(); 61 | }); 62 | sub2.use("/:c", sub3); 63 | sub1.use("/", function (req, _res, next) { 64 | urls.push("1@" + req.baseUrl); 65 | next(); 66 | }); 67 | sub1.use("/bar", sub2); 68 | sub1.use("/bar", function (req, _res, next) { 69 | urls.push("2@" + req.baseUrl); 70 | next(); 71 | }); 72 | app.use(function (req, _res, next) { 73 | urls.push("3@" + req.baseUrl); 74 | next(); 75 | }); 76 | app.use("/:a", sub1); 77 | app.use(function (req, res) { 78 | urls.push("4@" + req.baseUrl); 79 | res.end(urls.join(",")); 80 | }); 81 | 82 | superdeno(app) 83 | .get("/foo/bar/baz/zed") 84 | .expect(200, "3@,1@/foo,0@/foo/bar/baz,2@/foo/bar,4@", done); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/units/req.fresh.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("req", function () { 6 | describe(".fresh", function () { 7 | it("should return true when the resource is not modified", function (done) { 8 | const app = opine(); 9 | const etag = '"12345"'; 10 | 11 | app.use(function (req, res) { 12 | res.set("ETag", etag); 13 | res.send(req.fresh); 14 | }); 15 | 16 | superdeno(app) 17 | .get("/") 18 | .set("If-None-Match", etag) 19 | .expect(304, done); 20 | }); 21 | 22 | it("should return false when the resource is modified", function (done) { 23 | const app = opine(); 24 | 25 | app.use(function (req, res) { 26 | res.set("ETag", '"123"'); 27 | res.send(req.fresh); 28 | }); 29 | 30 | superdeno(app) 31 | .get("/") 32 | .set("If-None-Match", '"12345"') 33 | .expect(200, "false", done); 34 | }); 35 | 36 | it("should return false without response headers", function (done) { 37 | const app = opine(); 38 | 39 | app.disable("x-powered-by"); 40 | app.use(function (req, res) { 41 | res.send(req.fresh); 42 | }); 43 | 44 | superdeno(app) 45 | .get("/") 46 | .expect(200, "false", done); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/units/req.get.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { expect, superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("req", function () { 6 | describe(".get(field)", function () { 7 | it("should return the header field value", function (done) { 8 | const app = opine(); 9 | 10 | app.use(function (req, res) { 11 | expect(req.get("Something-Else")).toBeUndefined(); 12 | res.end(req.get("Content-Type") as string); 13 | }); 14 | 15 | superdeno(app) 16 | .post("/") 17 | .set("Content-Type", "application/json") 18 | .expect("application/json", done); 19 | }); 20 | 21 | it("should special-case Referer", function (done) { 22 | const app = opine(); 23 | 24 | app.use(function (req, res) { 25 | res.end(req.get("Referer") as string); 26 | }); 27 | 28 | superdeno(app) 29 | .post("/") 30 | .set("Referrer", "http://foobar.com") 31 | .expect("http://foobar.com", done); 32 | }); 33 | 34 | it("should special-case Referrer", function (done) { 35 | const app = opine(); 36 | 37 | app.use(function (req, res) { 38 | res.end(req.get("Referrer") as string); 39 | }); 40 | 41 | superdeno(app) 42 | .post("/") 43 | .set("Referer", "http://foobar.com") 44 | .expect("http://foobar.com", done); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/units/req.ips.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("req", function () { 6 | describe(".ips", function () { 7 | describe("when X-Forwarded-For is present", function () { 8 | describe('when "trust proxy" is enabled', function () { 9 | it("should return an array of the specified addresses", function ( 10 | done, 11 | ) { 12 | const app = opine(); 13 | 14 | app.enable("trust proxy"); 15 | 16 | app.use(function (req, res, _next) { 17 | res.send(req.ips); 18 | }); 19 | 20 | superdeno(app) 21 | .get("/") 22 | .set("X-Forwarded-For", "client, p1, p2") 23 | .expect('["client","p1","p2"]', done); 24 | }); 25 | 26 | it("should stop at first untrusted", function (done) { 27 | const app = opine(); 28 | 29 | app.set("trust proxy", 2); 30 | 31 | app.use(function (req, res, _next) { 32 | res.send(req.ips); 33 | }); 34 | 35 | superdeno(app) 36 | .get("/") 37 | .set("X-Forwarded-For", "client, p1, p2") 38 | .expect('["p1","p2"]', done); 39 | }); 40 | }); 41 | 42 | describe('when "trust proxy" is disabled', function () { 43 | it("should return an empty array", function (done) { 44 | const app = opine(); 45 | 46 | app.use(function (req, res, _next) { 47 | res.send(req.ips); 48 | }); 49 | 50 | superdeno(app) 51 | .get("/") 52 | .set("X-Forwarded-For", "client, p1, p2") 53 | .expect("[]", done); 54 | }); 55 | }); 56 | }); 57 | 58 | describe("when X-Forwarded-For is not present", function () { 59 | it("should return []", function (done) { 60 | const app = opine(); 61 | 62 | app.use(function (req, res, _next) { 63 | res.send(req.ips); 64 | }); 65 | 66 | superdeno(app) 67 | .get("/") 68 | .expect("[]", done); 69 | }); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/units/req.path.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("req", function () { 6 | describe(".path", function () { 7 | it("should return the parsed pathname", function (done) { 8 | const app = opine(); 9 | 10 | app.use(function (req, res) { 11 | res.end(req.path); 12 | }); 13 | 14 | superdeno(app) 15 | .get("/login?redirect=/post/1/comments") 16 | .expect("/login", done); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/units/req.route.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { expect, superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("req", function () { 6 | describe(".route", function () { 7 | it("should be the executed Route", function (done) { 8 | const app = opine(); 9 | 10 | app.get("/user/:id/:op?", function (req, res, next) { 11 | expect(req.route.path).toEqual("/user/:id/:op?"); 12 | next(); 13 | }); 14 | 15 | app.get("/user/:id/edit", function (req, res) { 16 | expect(req.route.path).toEqual("/user/:id/edit"); 17 | res.end(); 18 | }); 19 | 20 | superdeno(app) 21 | .get("/user/12/edit") 22 | .expect(200, done); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/units/req.secure.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("req", function () { 6 | describe(".secure", function () { 7 | describe("when X-Forwarded-Proto is missing", function () { 8 | it("should return false when http", function (done) { 9 | const app = opine(); 10 | 11 | app.get("/", function (req, res) { 12 | res.send(req.secure ? "yes" : "no"); 13 | }); 14 | 15 | superdeno(app) 16 | .get("/") 17 | .expect("no", done); 18 | }); 19 | }); 20 | }); 21 | 22 | describe(".secure", function () { 23 | describe("when X-Forwarded-Proto is present", function () { 24 | it("should return false when http", function (done) { 25 | const app = opine(); 26 | 27 | app.get("/", function (req, res) { 28 | res.send(req.secure ? "yes" : "no"); 29 | }); 30 | 31 | superdeno(app) 32 | .get("/") 33 | .set("X-Forwarded-Proto", "https") 34 | .expect("no", done); 35 | }); 36 | 37 | it('should return true when "trust proxy" is enabled', function (done) { 38 | const app = opine(); 39 | 40 | app.enable("trust proxy"); 41 | 42 | app.get("/", function (req, res) { 43 | res.send(req.secure ? "yes" : "no"); 44 | }); 45 | 46 | superdeno(app) 47 | .get("/") 48 | .set("X-Forwarded-Proto", "https") 49 | .expect("yes", done); 50 | }); 51 | 52 | it("should return false when initial proxy is http", function (done) { 53 | const app = opine(); 54 | 55 | app.enable("trust proxy"); 56 | 57 | app.get("/", function (req, res) { 58 | res.send(req.secure ? "yes" : "no"); 59 | }); 60 | 61 | superdeno(app) 62 | .get("/") 63 | .set("X-Forwarded-Proto", "http, https") 64 | .expect("no", done); 65 | }); 66 | 67 | it("should return true when initial proxy is https", function (done) { 68 | const app = opine(); 69 | 70 | app.enable("trust proxy"); 71 | 72 | app.get("/", function (req, res) { 73 | res.send(req.secure ? "yes" : "no"); 74 | }); 75 | 76 | superdeno(app) 77 | .get("/") 78 | .set("X-Forwarded-Proto", "https, http") 79 | .expect("yes", done); 80 | }); 81 | 82 | describe('when "trust proxy" trusting hop count', function () { 83 | it("should respect X-Forwarded-Proto", function (done) { 84 | const app = opine(); 85 | 86 | app.set("trust proxy", 1); 87 | 88 | app.get("/", function (req, res) { 89 | res.send(req.secure ? "yes" : "no"); 90 | }); 91 | 92 | superdeno(app) 93 | .get("/") 94 | .set("X-Forwarded-Proto", "https") 95 | .expect("yes", done); 96 | }); 97 | }); 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /test/units/req.stale.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("req", function () { 6 | describe(".stale", function () { 7 | it("should return false when the resource is not modified", function ( 8 | done, 9 | ) { 10 | const app = opine(); 11 | const etag = '"12345"'; 12 | 13 | app.use(function (req, res) { 14 | res.set("ETag", etag); 15 | res.send(req.stale); 16 | }); 17 | 18 | superdeno(app) 19 | .get("/") 20 | .set("If-None-Match", etag) 21 | .expect(304, done); 22 | }); 23 | 24 | it("should return true when the resource is modified", function (done) { 25 | const app = opine(); 26 | 27 | app.use(function (req, res) { 28 | res.set("ETag", '"123"'); 29 | res.send(req.stale); 30 | }); 31 | 32 | superdeno(app) 33 | .get("/") 34 | .set("If-None-Match", '"12345"') 35 | .expect(200, "true", done); 36 | }); 37 | 38 | it("should return true without response headers", function (done) { 39 | const app = opine(); 40 | 41 | app.use(function (req, res) { 42 | res.send(req.stale); 43 | }); 44 | 45 | superdeno(app) 46 | .get("/") 47 | .expect(200, "true", done); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/units/req.xhr.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { expect, superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("req", function () { 6 | describe(".xhr", function () { 7 | it("should return true when X-requested-With is xmlhttprequest", function ( 8 | done, 9 | ) { 10 | const app = opine(); 11 | 12 | app.use(function (req, res) { 13 | expect(req.xhr).toBe(true); 14 | res.end(); 15 | }); 16 | 17 | superdeno(app) 18 | .get("/") 19 | .set("X-requested-With", "xmlhttprequest") 20 | .expect(200, done); 21 | }); 22 | 23 | it("should case-insensitive", function (done) { 24 | const app = opine(); 25 | 26 | app.use(function (req, res) { 27 | expect(req.xhr).toBe(true); 28 | res.end(); 29 | }); 30 | 31 | superdeno(app) 32 | .get("/") 33 | .set("X-requested-With", "xmlhttprequest") 34 | .expect(200, done); 35 | }); 36 | 37 | it("should return false otherwise", function (done) { 38 | const app = opine(); 39 | 40 | app.use(function (req, res) { 41 | expect(req.xhr).toBe(false); 42 | res.end(); 43 | }); 44 | 45 | superdeno(app) 46 | .get("/") 47 | .set("X-requested-With", "blahblah") 48 | .expect(200, done); 49 | }); 50 | 51 | it("should return false when not present", function (done) { 52 | const app = opine(); 53 | 54 | app.use(function (req, res) { 55 | expect(req.xhr).toBe(false); 56 | res.end(); 57 | }); 58 | 59 | superdeno(app) 60 | .get("/") 61 | .expect(200, done); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/units/res.attachment.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("res", function () { 6 | describe(".attachment()", function () { 7 | it("should Content-Disposition to attachment", function (done) { 8 | const app = opine(); 9 | 10 | app.use(function (_req, res) { 11 | res.attachment().send("foo"); 12 | }); 13 | 14 | superdeno(app) 15 | .get("/") 16 | .expect("Content-Disposition", "attachment", done); 17 | }); 18 | }); 19 | 20 | describe(".attachment(filename)", function () { 21 | it("should add the filename param", function (done) { 22 | const app = opine(); 23 | 24 | app.use(function (_req, res) { 25 | res.attachment("/path/to/image.png"); 26 | res.send("foo"); 27 | }); 28 | 29 | superdeno(app) 30 | .get("/") 31 | .expect( 32 | "Content-Disposition", 33 | 'attachment; filename="image.png"', 34 | done, 35 | ); 36 | }); 37 | 38 | it("should set the Content-Type", function (done) { 39 | const app = opine(); 40 | 41 | app.use(function (_req, res) { 42 | res.attachment("/path/to/image.png"); 43 | res.send(new Uint8Array()); 44 | }); 45 | 46 | superdeno(app) 47 | .get("/") 48 | .expect("Content-Type", "image/png", done); 49 | }); 50 | }); 51 | 52 | describe(".attachment(utf8filename)", function () { 53 | it("should add the filename and filename* params", function (done) { 54 | const app = opine(); 55 | 56 | app.use(function (_req, res) { 57 | res.attachment("/locales/日本語.txt"); 58 | res.send("japanese"); 59 | }); 60 | 61 | superdeno(app) 62 | .get("/") 63 | .expect( 64 | "Content-Disposition", 65 | "attachment; filename=\"???.txt\"; filename*=UTF-8''%E6%97%A5%E6%9C%AC%E8%AA%9E.txt", 66 | ) 67 | .expect(200, done); 68 | }); 69 | 70 | it("should set the Content-Type", function (done) { 71 | const app = opine(); 72 | 73 | app.use(function (_req, res) { 74 | res.attachment("/locales/日本語.txt"); 75 | res.send("japanese"); 76 | }); 77 | 78 | superdeno(app) 79 | .get("/") 80 | .expect("Content-Type", "text/plain; charset=utf-8", done); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/units/res.cookie.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { expect, superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("res", function () { 6 | describe(".cookie(cookie)", function () { 7 | it("should set a cookie", function (done) { 8 | const app = opine(); 9 | 10 | app.use(function (req, res) { 11 | res.cookie({ name: "name", value: "deno" }).end(); 12 | }); 13 | 14 | superdeno(app) 15 | .get("/") 16 | .expect("Set-Cookie", "name=deno; Path=/") 17 | .expect(200, done); 18 | }); 19 | 20 | it("should raise an exception if no arg is provided", function (done) { 21 | const app = opine(); 22 | 23 | app.use(function (req, res) { 24 | (res as any).cookie().end(); 25 | }); 26 | 27 | superdeno(app) 28 | .get("/") 29 | .expect(500, done); 30 | }); 31 | 32 | it("should raise an expection if args provided are not valid", function ( 33 | done, 34 | ) { 35 | const app = opine(); 36 | 37 | app.use(function (req, res) { 38 | (res as any).cookie({}).end(); 39 | }); 40 | 41 | superdeno(app) 42 | .get("/") 43 | .expect(500, done); 44 | }); 45 | 46 | it("should allow multiple calls", function (done) { 47 | const app = opine(); 48 | 49 | app.use(function (req, res) { 50 | res.cookie({ name: "name", value: "deno" }); 51 | res.cookie({ name: "age", value: "1" }); 52 | res.cookie({ name: "gender", value: "?" }); 53 | res.end(); 54 | }); 55 | 56 | superdeno(app) 57 | .get("/") 58 | .end(function (err, res) { 59 | expect(res.header["set-cookie"]).toEqual( 60 | "name=deno; Path=/, age=1; Path=/, gender=?; Path=/", 61 | ); 62 | done(); 63 | }); 64 | }); 65 | }); 66 | 67 | describe(".cookie(name, string, options)", function () { 68 | it("should set params", function (done) { 69 | const app = opine(); 70 | 71 | app.use(function (req, res) { 72 | res.cookie("name", "deno", { httpOnly: true, secure: true }); 73 | res.end(); 74 | }); 75 | 76 | superdeno(app) 77 | .get("/") 78 | .expect("Set-Cookie", "name=deno; Secure; HttpOnly; Path=/") 79 | .expect(200, done); 80 | }); 81 | 82 | describe("maxAge", function () { 83 | it("should set max-age", function (done) { 84 | const app = opine(); 85 | 86 | app.use(function (req, res) { 87 | res.cookie("name", "deno", { maxAge: 1000 }); 88 | res.end(); 89 | }); 90 | 91 | superdeno(app) 92 | .get("/") 93 | .expect("Set-Cookie", /Max-Age=1/, done); 94 | }); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/units/res.get.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("res", function () { 6 | describe(".get(field)", function () { 7 | it("should get the response header field", function (done) { 8 | const app = opine(); 9 | 10 | app.use(function (req, res) { 11 | res.set("Content-Type", "text/x-foo"); 12 | res.send(res.get("Content-Type")); 13 | }); 14 | 15 | superdeno(app).get("/").expect(200, /text\/x\-foo/, done); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/units/res.links.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("res", function () { 6 | describe(".links(obj)", function () { 7 | it("should set Link header field", function (done) { 8 | const app = opine(); 9 | 10 | app.use(function (req, res) { 11 | res.links({ 12 | next: "http://api.example.com/users?page=2", 13 | last: "http://api.example.com/users?page=5", 14 | }); 15 | res.end(); 16 | }); 17 | 18 | superdeno(app) 19 | .get("/") 20 | .expect( 21 | "Link", 22 | '; rel="next", ; rel="last"', 23 | ) 24 | .expect(200, done); 25 | }); 26 | 27 | it("should set Link header field for multiple calls", function (done) { 28 | const app = opine(); 29 | 30 | app.use(function (req, res) { 31 | res.links({ 32 | next: "http://api.example.com/users?page=2", 33 | last: "http://api.example.com/users?page=5", 34 | }); 35 | 36 | res.links({ 37 | prev: "http://api.example.com/users?page=1", 38 | }); 39 | 40 | res.end(); 41 | }); 42 | 43 | superdeno(app) 44 | .get("/") 45 | .expect( 46 | "Link", 47 | '; rel="next", ; rel="last", ; rel="prev"', 48 | ) 49 | .expect(200, done); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/units/res.locals.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { superdeno } from "../deps.ts"; 3 | import { describe, it } from "../utils.ts"; 4 | 5 | describe("res", function () { 6 | describe(".locals", function () { 7 | it("should be empty by default", function (done) { 8 | const app = opine(); 9 | 10 | app.use(function (req, res) { 11 | res.json(res.locals); 12 | }); 13 | 14 | superdeno(app) 15 | .get("/") 16 | .expect(200, {}, done); 17 | }); 18 | }); 19 | 20 | it("should work when mounted", function (done) { 21 | const app = opine(); 22 | const blog = opine(); 23 | 24 | app.use(blog); 25 | 26 | blog.use(function (req, res, next) { 27 | res.locals.foo = "bar"; 28 | next(); 29 | }); 30 | 31 | app.use(function (req, res) { 32 | res.json(res.locals); 33 | }); 34 | 35 | superdeno(app) 36 | .get("/") 37 | .expect(200, { foo: "bar" }, done); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/units/res.removeHeader.test.ts: -------------------------------------------------------------------------------- 1 | import opine from "../../mod.ts"; 2 | import { describe, it } from "../utils.ts"; 3 | import { superdeno } from "../deps.ts"; 4 | import { expect } from "../deps.ts"; 5 | 6 | describe("res", function () { 7 | describe(".removeHeader(field)", function () { 8 | it("should remove the response header field", function (done) { 9 | const app = opine(); 10 | 11 | app.use(function (req, res) { 12 | res.set("Content-Type", "text/x-foo; charset=utf-8"); 13 | res.removeHeader("Content-Type").end(); 14 | }); 15 | 16 | superdeno(app) 17 | .get("/") 18 | .end((_, res) => { 19 | expect(res.header).not.toHaveProperty("Content-Type"); 20 | done(); 21 | }); 22 | }); 23 | 24 | it("should do nothing if header is not present", function (done) { 25 | const app = opine(); 26 | 27 | app.use(function (req, res) { 28 | res.removeHeader("Content-Type").end(); 29 | }); 30 | 31 | superdeno(app) 32 | .get("/") 33 | .expect(200, done); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/units/res.set.test.ts: -------------------------------------------------------------------------------- 1 | import opine from "../../mod.ts"; 2 | import { describe, it } from "../utils.ts"; 3 | import { superdeno } from "../deps.ts"; 4 | import { expect } from "../deps.ts"; 5 | 6 | describe("res", function () { 7 | describe(".set(field, value)", function () { 8 | it("should set the response header field", function (done) { 9 | const app = opine(); 10 | 11 | app.use(function (req, res) { 12 | res.set("Content-Type", "text/x-foo; charset=utf-8").end(); 13 | }); 14 | 15 | superdeno(app) 16 | .get("/") 17 | .expect("Content-Type", "text/x-foo; charset=utf-8") 18 | .end(done); 19 | }); 20 | 21 | it("should coerce value to string", function (done) { 22 | const app = opine(); 23 | 24 | app.use(function (req, res) { 25 | res.set("X-Number", 123 as any); 26 | res.set( 27 | "Access-Control-Allow-Methods", 28 | ["POST", "GET", "OPTIONS"] as any, 29 | ); 30 | res.end(typeof res.get("X-Number")); 31 | }); 32 | 33 | superdeno(app) 34 | .get("/") 35 | .expect("Access-Control-Allow-Methods", "POST,GET,OPTIONS") 36 | .expect("X-Number", "123") 37 | .expect(200, "string", done); 38 | }); 39 | 40 | it("should not set a charset of one is already set", function (done) { 41 | const app = opine(); 42 | 43 | app.use(function (req, res) { 44 | res.set("Content-Type", "text/html; charset=lol"); 45 | res.end(); 46 | }); 47 | 48 | superdeno(app) 49 | .get("/") 50 | .expect("Content-Type", "text/html; charset=lol") 51 | .expect(200, done); 52 | }); 53 | }); 54 | 55 | describe(".set(object)", function () { 56 | it("should set multiple fields", function (done) { 57 | const app = opine(); 58 | 59 | app.use(function (req, res) { 60 | res.set({ 61 | "X-Foo": "bar", 62 | "X-Bar": "baz", 63 | }).end(); 64 | }); 65 | 66 | superdeno(app) 67 | .get("/") 68 | .expect("X-Foo", "bar") 69 | .expect("X-Bar", "baz") 70 | .end(done); 71 | }); 72 | 73 | it("should coerce value to a string", function (done) { 74 | const app = opine(); 75 | 76 | app.use(function (req, res) { 77 | res.set( 78 | { 79 | "X-Number": 123 as any, 80 | "Access-Control-Allow-Methods": ["POST", "GET", "OPTIONS"] as any, 81 | }, 82 | ); 83 | res.end(typeof res.get("X-Number")); 84 | }); 85 | 86 | superdeno(app) 87 | .get("/") 88 | .expect("X-Number", "123") 89 | .expect("access-control-allow-methods", "POST,GET,OPTIONS") 90 | .expect(200, "string", done); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/units/res.setHeader.test.ts: -------------------------------------------------------------------------------- 1 | import opine from "../../mod.ts"; 2 | import { describe, it } from "../utils.ts"; 3 | import { expect, mock, superdeno } from "../deps.ts"; 4 | 5 | describe("res", function () { 6 | describe(".setHeader(field, value)", function () { 7 | it("should passthrough to .set", function (done) { 8 | const app = opine(); 9 | 10 | const set = mock.fn(() => undefined); 11 | app.use(function (_req, res) { 12 | res.set = set; 13 | res.setHeader("Content-Type", "text/x-foo; charset=utf-8").end(); 14 | }); 15 | 16 | superdeno(app) 17 | .get("/") 18 | .end(() => { 19 | expect(set).toHaveBeenCalledWith( 20 | "Content-Type", 21 | "text/x-foo; charset=utf-8", 22 | ); 23 | done(); 24 | }); 25 | }); 26 | }); 27 | 28 | describe(".setHeader(object)", function () { 29 | it("should passthrough to .set", function (done) { 30 | const app = opine(); 31 | 32 | const set = mock.fn(() => undefined); 33 | app.use(function (_req, res) { 34 | res.set = set; 35 | res.setHeader({ 36 | "X-Foo": "bar", 37 | "X-Bar": "baz", 38 | }).end(); 39 | }); 40 | 41 | superdeno(app) 42 | .get("/") 43 | .end(() => { 44 | expect(set).toHaveBeenCalledWith({ 45 | "X-Foo": "bar", 46 | "X-Bar": "baz", 47 | }, undefined); 48 | done(); 49 | }); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/units/res.setStatus.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { describe, it } from "../utils.ts"; 3 | import { superdeno } from "../deps.ts"; 4 | 5 | describe("res", function () { 6 | describe(".status(code)", function () { 7 | it("should set the response .statusCode", function (done) { 8 | const app = opine(); 9 | 10 | app.use(function (req, res) { 11 | res.setStatus(201).end("Created"); 12 | }); 13 | 14 | superdeno(app) 15 | .get("/") 16 | .expect("Created") 17 | .expect(201, done); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/units/res.type.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { describe, it } from "../utils.ts"; 3 | import { superdeno } from "../deps.ts"; 4 | 5 | describe("res", function () { 6 | describe(".type(str)", function () { 7 | it("should set the Content-Type based on a filename", function (done) { 8 | const app = opine(); 9 | 10 | app.use(function (req, res) { 11 | res.type("foo.js").end('var name = "deno";'); 12 | }); 13 | 14 | superdeno(app) 15 | .get("/") 16 | .expect("Content-Type", "application/javascript; charset=utf-8") 17 | .end(done); 18 | }); 19 | 20 | it("should default to application/octet-stream", function (done) { 21 | const app = opine(); 22 | 23 | app.use(function (req, res) { 24 | res.type("rawr").end('var name = "deno";'); 25 | }); 26 | 27 | superdeno(app) 28 | .get("/") 29 | .expect("Content-Type", "application/octet-stream", done); 30 | }); 31 | 32 | it("should set the Content-Type with type/subtype", function (done) { 33 | const app = opine(); 34 | 35 | app.use(function (req, res) { 36 | res.type("application/vnd.amazon.ebook") 37 | .end('var name = "deno";'); 38 | }); 39 | 40 | superdeno(app) 41 | .get("/") 42 | .expect("Content-Type", "application/vnd.amazon.ebook", done); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/units/res.vary.test.ts: -------------------------------------------------------------------------------- 1 | import { opine } from "../../mod.ts"; 2 | import { describe, it } from "../utils.ts"; 3 | import { superdeno } from "../deps.ts"; 4 | 5 | describe("res.vary()", function () { 6 | describe("with a string", function () { 7 | it("should set the value", function (done) { 8 | const app = opine(); 9 | 10 | app.use(function (req, res) { 11 | res.vary("Accept"); 12 | res.end(); 13 | }); 14 | 15 | superdeno(app) 16 | .get("/") 17 | .expect("vary", /Accept/) 18 | .expect(200, done); 19 | }); 20 | }); 21 | 22 | describe("when the value is present", function () { 23 | it("should not add it again", function (done) { 24 | const app = opine(); 25 | 26 | app.use(function (_req, res) { 27 | res.vary("Accept"); 28 | res.vary("Accept-Encoding"); 29 | res.vary("Accept-Encoding"); 30 | res.vary("Accept-Encoding"); 31 | res.vary("Accept"); 32 | res.end(); 33 | }); 34 | 35 | superdeno(app) 36 | .get("/") 37 | .expect("Vary", "Accept, Accept-Encoding") 38 | .expect(200, done); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Version of Opine. 3 | */ 4 | export const VERSION = "2.3.4"; 5 | 6 | /** 7 | * Supported version of Deno. 8 | */ 9 | export const DENO_SUPPORTED_VERSIONS: string[] = ["1.32.4"]; 10 | --------------------------------------------------------------------------------