Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

A middleware framework for handling HTTP with Deno, Node, Bun and Cloudflare Workers 🐿️ 🦕

License

NotificationsYou must be signed in to change notification settings

oakserver/oak

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jsr.io/@oak/oakjsr.io/@oak/oak scoredeno.land/x/oaknpm Version

oak cicodecov

A middleware framework for Deno's native HTTP server,Deno Deploy, Node.js 16.5 and later,Cloudflare Workers andBun. It also includes a middleware router.

This middleware framework is inspired byKoaand middleware router inspired by@koa/router.

This README focuses on the mechanics of the oak APIs and is intended for thosewho are familiar with JavaScript middleware frameworks like Express and Koa aswell as a decent understanding of Deno. If you aren't familiar with these,please check out documentation onoakserver.github.io/oak.

Also, check out ourFAQs and theawesome-oak site of communityresources.

Note

The examples in this README pull frommain and are designed for Deno CLI orDeno Deploy, which may not make sense to do when you are looking to actuallydeploy a workload. You would want to "pin" to a particular version which iscompatible with the version of Deno you are using and has a fixed set of APIsyou would expect.https://deno.land/x/ supports using git tags in the URL todirect you at a particular version. So to use version 13.0.0 of oak, you wouldwant to importhttps://deno.land/x/oak@v13.0.0/mod.ts.

Usage

Deno CLI and Deno Deploy

oak is available on bothdeno.land/x andJSR. To use fromdeno.land/x, import into a module:

import{Application}from"https://deno.land/x/oak/mod.ts";

To use from JSR, import into a module:

import{Application}from"jsr:@oak/oak";

Or use the Deno CLI to add it to your project:

deno add jsr:@oak/oak

Node.js

oak is available for Node.js on bothnpm andJSR. To use from npm, install the package:

npm i @oakserver/oak

And then import into a module:

import{Application}from"@oakserver/oak";

To use from JSR, install the package:

npx jsr i @oak/oak

And then import into a module:

import{Application}from"@oak/oak/application";

Note

Send, websocket upgrades and serving over TLS/HTTPS are not currentlysupported.

In addition the Cloudflare Worker environment and execution context are notcurrently exposed to middleware.

Cloudflare Workers

oak is available forCloudflare Workers onJSR. To use add the package to your Cloudflare Workerproject:

npx jsr add @oak/oak

And then import into a module:

import{Application}from"@oak/oak/application";

Unlike other runtimes, the oak application doesn't listen for incoming requests,instead it handles worker fetch requests. A minimal example server would be:

import{Application}from"@oak/oak/application";constapp=newApplication();app.use((ctx)=>{ctx.response.body="Hello CFW!";});exportdefault{fetch:app.fetch};

Note

Send and websocket upgrades are not currently supported.

Bun

oak is available for Bun onJSR. To use install thepackage:

bunx jsr i @oak/oak

And then import into a module:

import{Application}from"@oak/oak/application";

Note

Send and websocket upgrades are not currently supported.

Application, middleware, and context

TheApplication class coordinates managing the HTTP server, runningmiddleware, and handling errors that occur when processing requests. Two of themethods are generally used:.use() and.listen(). Middleware is added viathe.use() method and the.listen() method will start the server and startprocessing requests with the registered middleware.

A basic usage, responding to every request withHello World!:

import{Application}from"jsr:@oak/oak/application";constapp=newApplication();app.use((ctx)=>{ctx.response.body="Hello World!";});awaitapp.listen({port:8000});

You would then run this script in Deno like:

> deno run --allow-net helloWorld.ts

For more information on running code under Deno, or information on how toinstall the Deno CLI, check out theDeno manual.

The middleware is processed as a stack, where each middleware function cancontrol the flow of the response. When the middleware is called, it is passed acontext and reference to the "next" method in the stack.

A more complex example:

import{Application}from"jsr:@oak/oak/application";constapp=newApplication();// Loggerapp.use(async(ctx,next)=>{awaitnext();constrt=ctx.response.headers.get("X-Response-Time");console.log(`${ctx.request.method}${ctx.request.url} -${rt}`);});// Timingapp.use(async(ctx,next)=>{conststart=Date.now();awaitnext();constms=Date.now()-start;ctx.response.headers.set("X-Response-Time",`${ms}ms`);});// Hello World!app.use((ctx)=>{ctx.response.body="Hello World!";});awaitapp.listen({port:8000});

To provide an HTTPS server, then theapp.listen() options need to include theoptions.secure option set totrue and supply a.certFile and a.keyFileoptions as well.

.handle() method

The.handle() method is used to process requests and receive responses withouthaving the application manage the server aspect. This though is advanced usageand most users will want to use.listen().

The.handle() method accepts up to three arguments. The first being aRequest argument,and the second being aDeno.Conn argument. The third optional argument is aflag to indicate if the request was "secure" in the sense it originated from aTLS connection to the remote client. The method resolved with aResponse objectorundefined if thectx.respond === true.

An example:

import{Application}from"jsr:@oak/oak/application";constapp=newApplication();app.use((ctx)=>{ctx.response.body="Hello World!";});Deno.serve(async(request,info)=>{constres=awaitapp.handle(request,info.remoteAddr);returnres??Response.error();});

An instance of application has some properties as well:

  • contextState - Determines the method used to create state for a new context.A value of"clone" will set the state as a clone of the app state. A valueof"prototype" means the app's state will be used as the prototype of thecontext's state. A value of"alias" means that the application's state andthe context's state will be a reference to the same object. A value of"empty" will initialize the context's state with an empty object.

  • .jsonBodyReplacer - An optional replacer function which will be applied toJSON bodies when forming a response.

  • .jsonBodyReviver - An optional reviver function which will be applied whenreading JSON bodies in a request.

  • .keys

    Keys to be used when signing and verifying cookies. The value can be set to anarray of keys, and instance ofKeyStack, or an object which provides thesame interface asKeyStack (e.g. an instance ofkeygrip). If just the keys arepassed, oak will manage the keys viaKeyStack which allows easy key rotationwithout requiring re-signing of data values.

  • .proxy

    This defaults tofalse, but can be set via theApplication constructoroptions. This is intended to indicate the application is behind a proxy andwill useX-Forwarded-Proto,X-Forwarded-Host, andX-Forwarded-For whenprocessing the request, which should provide more accurate information aboutthe request.

  • .state

    A record of application state, which can be strongly typed by specifying ageneric argument when constructing anApplication(), or inferred by passinga state object (e.g.Application({ state })).

Context

The context passed to middleware has several properties:

  • .app

    A reference to theApplication that is invoking this middleware.

  • .cookies

    TheCookies instance for this context which allows you to read and setcookies.

  • .request

    TheRequest object which contains details about the request.

  • .respond

    Determines if when middleware finishes processing, the application should sendthe.response to the client. Iftrue the response will be sent, and iffalse the response will not be sent. The default istrue but certainmethods, like.upgrade() and.sendEvents() will set this tofalse.

  • .response

    TheResponse object which will be used to form the response sent back to therequestor.

  • .socket

    This will beundefined if the connection has not been upgraded to a websocket. If the connection has been upgraded, the.socket interface will beset.

  • .state

    A record of application state, which can be strongly typed by specifying ageneric argument when constructing anApplication(), or inferred by passinga state object (e.g.Application({ state })).

The context passed to middleware has some methods:

  • .assert()

    Makes an assertion, which if not true, throws anHTTPError, which subclassis identified by the second argument, with the message being the thirdargument.

  • .send()

    Stream a file to the requesting client. SeeStatic contentbelow for more information.

  • .sendEvents()

    Convert the current connection into a server-sent event response and return aServerSentEventTarget where messages and events can be streamed to theclient. This will set.respond tofalse.

  • .throw()

    Throws anHTTPError, which subclass is identified by the first argument,with the message being passed as the second.

  • .upgrade()

    Attempt to upgrade the connection to a web socket connection, and return aWebSocket interface. Previous version of oak, this would be aPromiseresolving with astd/ws web socket.

Unlike other middleware frameworks,context does not have a significant amountof aliases. The information about the request is only located in.request andthe information about the response is only located in.response.

Cookies

Thecontext.cookies allows access to the values of cookies in the request, andallows cookies to be set in the response. It automatically secures cookies ifthe.keys property is set on the application. Because.cookies uses the webcrypto APIs to sign and validate cookies, and those APIs work in an asynchronousway, the cookie APIs work in an asynchronous way. It has several methods:

  • .get(key: string, options?: CookieGetOptions): Promise<string | undefined>

    Attempts to retrieve the cookie out of the request and returns the value ofthe cookie based on the key. If the applications.keys is set, then thecookie will be verified against a signed version of the cookie. If the cookieis valid, the promise will resolve with the value. If it is invalid, thecookie signature will be set to deleted on the response. If the cookie was notsigned by the current key, it will be resigned and added to the response.

  • .set(key: string, value: string, options?: CookieSetDeleteOptions): Promise<void>

    Will set a cookie in the response based on the provided key, value and anyoptions. If the applications.keys is set, then the cookie will be signedand the signature added to the response. As the keys are signedasynchronously, awaiting the.set() method is advised.

Request

Thecontext.request contains information about the request. It containsseveral properties:

  • .body

    An object which provides access to the body of the request. See below fordetails about the request body API.

  • .hasBody

    Set totrue if the request might have a body, orfalse if it does not. Itdoes not validate if the body is supported by the built in body parser though.

    [!WARNING]This is an unreliable API. In HTTP/2 in many situations you cannot determineif a request has a body or not unless you attempt to read the body, due tothe streaming nature of HTTP/2. As of Deno 1.16.1, for HTTP/1.1, Denoalso reflects that behavior. The only reliable way to determine if a requesthas a body or not is to attempt to read the body.

    It is best to determine if a body might be meaningful to you with a givenmethod, and then attempt to read and process the body if it is meaningful in agiven context. For exampleGET andHEAD should never have a body, butmethods likeDELETE andOPTIONSmight have a body and should be havetheir body conditionally processed if it is meaningful to your application.

  • .headers

    The headers for the request, an instance ofHeaders.

  • .method

    A string that represents the HTTP method for the request.

  • .originalRequest

    DEPRECATED this will be removed in a future release of oak.

    The "raw"NativeServer request, which is an abstraction over the DOMRequest object..originalRequest.request is the DOMRequest instancethat is being processed. Users should generally avoid using these.

  • .secure

    A shortcut for.protocol, returningtrue if HTTPS otherwisefalse.

  • .source

    When running under Deno,.source will be set to the source web standardRequest. When running under NodeJS, this will beundefined.

  • .url

    An instance ofURLwhich is based on the full URL for the request. This is in place of havingparts of the URL exposed on the rest of the request object.

And several methods:

  • .accepts(...types: string[])

    Negotiates the content type supported by the request for the response. If nocontent types are passed, the method returns a prioritized array of acceptedcontent types. If content types are passed, the best negotiated content typeis returned. If no content type matchundefined is returned.

  • .acceptsEncodings(...encodings: string[])

    Negotiates the content encoding supported by the request for the response. Ifno encodings are passed, the method returns a prioritized array of acceptedencodings. If encodings are passed, the best negotiated encoding is returned.If no encodings matchundefined is returned.

  • .acceptsLanguages(...languages: string[])

    Negotiates the language the client is able to understand. Where a localevariant takes preference. If no encodings are passed, the method returns aprioritized array of understood languages. If languages are passed, the bestnegotiated language is returned. If no languages matchundefined isreturned.

Request Body

Important

This API changed significantly in oak v13 and later. The previous API hadgrown organically since oak was created in 2018 and didn't represent any othercommon API. The API introduced in v13 aligns better to the Fetch API'sRequest way of dealing with the body, and should be more familiar todevelopers coming to oak for the first time.

The API for the oak request.body is inspired by the Fetch API'sRequest butwith some add functionality. The context'srequest.body is an instance of anobject which provides several properties:

  • .has

    Set totrue if the request might have a body, orfalse if it does not. Itdoes not validate if the body is supported by the built in body parser though.

    [!IMPORTANT]This is an unreliable API. In HTTP/2 in many situations you cannot determineif a request has a body or not unless you attempt to read the body, due tothe streaming nature of HTTP/2. As of Deno 1.16.1, for HTTP/1.1, Denoalso reflects that behavior. The only reliable way to determine if a requesthas a body or not is to attempt to read the body.

    It is best to determine if a body might be meaningful to you with a givenmethod, and then attempt to read and process the body if it is meaningful in agiven context. For exampleGET andHEAD should never have a body, butmethods likeDELETE andOPTIONSmight have a body and should be havetheir body conditionally processed if it is meaningful to your application.

  • .stream

    AReadableStream<Uint8Array> that will allow reading of the body inUint8Array chunks. This is akin the.body property in a Fetch APIRequest.

  • .used

    Set totrue if the body has been used, otherwise set tofalse.

It also has several methods:

  • arrayBuffer()

    Resolves with anArrayBuffer that contains the contents of the body, if any.Suitable for reading/handling binary data.

  • blob()

    Resolves with aBlob that contains the contents of the body. Suitable forreading/handling binary data.

  • form()

    Resolves with aURLSearchParams which has been decoded from the contents ofa body. This is appropriate for a body with a content type ofapplication/x-www-form-urlencoded.

  • formData()

    Resolves with aFormData instance which has been decoded from the contentsof a body. This is appropriate for a body with a content type ofmultipart/form-data.

  • json()

    Resolves with the data from the body parsed as JSON. If ajsonBodyReviverhas been specified in the application, it will be used when parsing the JSON.

  • text()

    Resolves with a string that represents the contents of the body.

  • type()

    Attempts to provide guidance of how the body is encoded which can be used todetermine what method to use to decode the body. The method returns a stringthat represents the interpreted body type:

    ValueDescription
    "binary"The body has a content type that indicates binary data and the.arrayBuffer(),.blob() or.stream should be used to read the body.
    "form"The body is encoded as form data and.form() should be used to read the body.
    "form-data"The body is encoded as a multi-part form and.formData() should be used to read the body.
    "json"The body is encoded as JSON data and.json() should be used to read the body.
    "text"The body is encoded as text and.text() should be used to read the body.
    "unknown"Either there is no body or it was not possible to determine the body type based on the content type.

    The.type() method also takes an optional argument of custom media typesthat will be used when attempting to determine the type of the body. These arethen incorporated into the default media types. The value is an object wherethe key is one ofbinary,form,form-data,json, ortext and thevalue is the appropriate media type in a format compatible with thetype-is format.

Response

Thecontext.response contains information about the response which will besent back to the requestor. It contains several properties:

  • .body

    The body of the response, which can often be handled by the automatic responsebody handling documented below.

  • .headers

    AHeaders instance which contains the headers for the response.

  • .status

    An HTTPStatus code that will be sent back with the response. If this is notset before responding, oak will default to200 OK if there is a.body,otherwise404 Not Found.

  • .type

    A media type or extension to set theContent-Type header for the response.For example, you can providetxt ortext/plain to describe the body.

And several methods:

  • .redirect(url?: string | URL | REDIRECT_BACK, alt?: string | URL)

    A method to simplify redirecting the response to another URL. It will set theLocation header to the suppliedurl and the status to302 Found (unlessthe status is already a3XX status). The use of symbolREDIRECT_BACK astheurl indicates that theReferer header in the request should be used asthe direction, with thealt being the alternative location if theRefereris not set. If neither thealt nor theReferer are set, the redirect willoccur to/. A basic HTML (if the requestor supports it) or a text body willbe set explaining they are being redirected.

  • .toDomResponse()

    This converts the information oak understands about the response to the FetchAPIResponse. This will finalize the response, resulting in any furtherattempt to modify the response to throw. This is intended to be usedinternally within oak to be able to respond to requests.

  • .with(response: Response) and.with(body?: BodyInit, init?: ResponseInit)

    This sets the response to a web standardResponse. Note that this willignore/override any other information set on the response by other middlewareincluding things like headers or cookies to be set.

Automatic response body handling

When the responseContent-Type is not set in the headers of the.response,oak will automatically try to determine the appropriateContent-Type. First itwill look at.response.type. If assigned, it will try to resolve theappropriate media type based on treating the value of.type as either themedia type, or resolving the media type based on an extension. For example if.type was set to"html", then theContent-Type will be set to"text/html".

If.type is not set with a value, then oak will inspect the value of.response.body. If the value is astring, then oak will check to see if thestring looks like HTML, if so,Content-Type will be set totext/htmlotherwise it will be set totext/plain. If the value is an object, other thanaUint8Array, aDeno.Reader, ornull, the object will be passed toJSON.stringify() and theContent-Type will be set toapplication/json.

If the type of body is a number, bigint or symbol, it will be coerced to astring and treated as text.

If the value of body is a function, the function will be called with noarguments. If the return value of the function is promise like, that will beawait, and the resolved value will be processed as above. If the value is notpromise like, it will be processed as above.

Opening the server

The application method.listen() is used to open the server, start listeningfor requests, and processing the registered middleware for each request. Thismethod returns a promise when the server closes.

Once the server is open, before it starts processing requests, the applicationwill fire a"listen" event, which can be listened for via the.addEventListener() method. For example:

import{Application}from"jsr:@oak/oak/application";constapp=newApplication();app.addEventListener("listen",({ hostname, port, secure})=>{console.log(`Listening on:${secure ?"https://" :"http://"}${hostname??"localhost"}:${port}`,);});// register some middlewareawaitapp.listen({port:80});

Closing the server

If you want to close the application, the application supports the option of anabort signal.Here is an example of using the signal:

import{Application}from"jsr:@oak/oak/application";constapp=newApplication();constcontroller=newAbortController();const{ signal}=controller;// Add some middleware using `app.use()`constlistenPromise=app.listen({port:8000, signal});// In order to close the server...controller.abort();// Listen will stop listening for requests and the promise will resolve...awaitlistenPromise;// and you can do something after the close to shutdown

Error handling

Middleware can be used to handle other errors with middleware. Awaiting othermiddleware to execute while trapping errors works. So if you had an errorhandling middleware that provides a well managed response to errors would worklike this:

import{Application}from"jsr:@oak/oak/application";import{isHttpError}from"jsr:@oak/commons/http_errors";import{Status}from"jsr:@oak/commons/status";constapp=newApplication();app.use(async(ctx,next)=>{try{awaitnext();}catch(err){if(isHttpError(err)){switch(err.status){caseStatus.NotFound:// handle NotFoundbreak;default:// handle other statuses}}else{// rethrow if you can't handle the errorthrowerr;}}});

Uncaught middleware exceptions will be caught by the application.Applicationextends the globalEventTarget in Deno, and when uncaught errors occur in themiddleware or sending of responses, anEventError will be dispatched to theapplication. To listen for these errors, you would add an event handler to theapplication instance:

import{Application}from"jsr:@oak/oak/application";constapp=newApplication();app.addEventListener("error",(evt)=>{// Will log the thrown error to the console.console.log(evt.error);});app.use((ctx)=>{// Will throw a 500 on every request.ctx.throw(500);});awaitapp.listen({port:80});

Router

TheRouter class produces middleware which can be used with anApplicationto enable routing based on the pathname of the request.

Basic usage

The following example serves up aRESTful service of a map of books, wherehttp://localhost:8000/book/ will return an array of books andhttp://localhost:8000/book/1 would return the book with ID"1":

import{Application}from"jsr:@oak/oak/application";import{Router}from"jsr:@oak/oak/router";constbooks=newMap<string,any>();books.set("1",{id:"1",title:"The Hound of the Baskervilles",author:"Conan Doyle, Arthur",});constrouter=newRouter();router.get("/",(context)=>{context.response.body="Hello world!";}).get("/book",(context)=>{context.response.body=Array.from(books.values());}).get("/book/:id",(context)=>{if(books.has(context?.params?.id)){context.response.body=books.get(context.params.id);}});constapp=newApplication();app.use(router.routes());app.use(router.allowedMethods());awaitapp.listen({port:8000});

A route passed is converted to a regular expression usingpath-to-regexp, which meansparameters expressed in the pattern will be converted.path-to-regexp hasadvanced usage which can create complex patterns which can be used for matching.Check out thedocumentation for that libraryif you have advanced use cases.

In most cases, the type ofcontext.params is automatically inferred from thepath template string through typescript magic. In more complex scenarios thismight not yield the correct result however. In that case you can override thetype withrouter.get<RouteParams>, whereRouteParams is the explicit typeforcontext.params.

Nested routers

Nesting routers is supported. The following example responds tohttp://localhost:8000/forums/oak/posts andhttp://localhost:8000/forums/oak/posts/nested-routers.

import{Application}from"jsr:@oak/oak/application";import{Router}from"jsr:@oak/oak/router";constposts=newRouter().get("/",(ctx)=>{ctx.response.body=`Forum:${ctx.params.forumId}`;}).get("/:postId",(ctx)=>{ctx.response.body=`Forum:${ctx.params.forumId}, Post:${ctx.params.postId}`;});constforums=newRouter().use("/forums/:forumId/posts",posts.routes(),posts.allowedMethods(),);awaitnewApplication().use(forums.routes()).listen({port:8000});

Static content

The functionsend() is designed to serve static content as part of amiddleware function. In the most straight forward usage, a root is provided andrequests provided to the function are fulfilled with files from the local filesystem relative to the root from the requested path.

A basic usage would look something like this:

import{Application}from"jsr:@oak/oak/application";constapp=newApplication();app.use(async(context,next)=>{try{awaitcontext.send({root:`${Deno.cwd()}/examples/static`,index:"index.html",});}catch{awaitnext();}});awaitapp.listen({port:8000});

send() automatically supports features like providingETag andLast-Modified headers in the response as well as processingIf-None-MatchandIf-Modified-Since headers in the request. This means when serving upstatic content, clients will be able to rely upon their cached versions ofassets instead of re-downloading them.

ETag support

Thesend() method automatically supports generating anETag header forstatic assets. The header allows the client to determine if it needs tore-download an asset or not, but it can be useful to calculateETags for otherscenarios.

There is a middleware function that assesses thecontext.reponse.body anddetermines if it can create anETag header for that body type, and if so setstheETag header on the response. Basic usage would look something like this:

import{Application}from"jsr:@oak/oak/application";import{factory}from"jsr:@oak/oak/etag";constapp=newApplication();app.use(factory());// ... other middleware for the application

There is also a function which retrieves an entity for a given context based onwhat it logical to read into memory which can be passed to the etag calculatethat is part of the Deno std library:

import{Application}from"jsr:@oak/oak/application";import{getEntity}from"jsr:@oak/oak/etag";import{calculate}from"jsr:@std/http/etag";constapp=newApplication();// The context.response.body has already been set...app.use(async(ctx)=>{constentity=awaitgetEntity(ctx);if(entity){constetag=awaitcalculate(entity);}});

Fetch API andDeno.serve() migration

If you are migrating fromDeno.serve() or adapting code that is designed forthe web standard Fetch APIRequest andResponse, there are a couple featuresof oak to assist.

ctx.request.source

When running under Deno, this will be set to a Fetch APIRequest, givingdirect access to the original request.

ctx.response.with()

This method will accept a Fetch APIResponse or create a new response basedon the providedBodyInit andResponseInit. This will also finalize theresponse and ignores anything that may have been set on the oak.response.

middleware/serve#serve() andmiddelware/serve#route()

These two middleware generators can be used to adapt code that operates morelike theDeno.serve() in that it provides a Fetch APIRequest and expectsthe handler to resolve with a Fetch APIResponse.

An example of usingserve() withApplication.prototype.use():

import{Application}from"jsr:@oak/oak/application";import{serve}from"jsr:@oak/oak/serve";constapp=newApplication();app.use(serve((req,ctx)=>{console.log(req.url);returnnewResponse("Hello world!");}));app.listen();

And a similar solution works withroute() where the context contains theinformation about the router, like the params:

import{Application}from"jsr:@oak/oak/application";import{Router}from"jsr:@oak/oak/router";import{route}from"jsr:@oak/oak/serve";constapp=newApplication;constrouter=newRouter();router.get("/books/:id",route((req,ctx)=>{console.log(ctx.params.id);returnResponse.json({title:"hello world",id:ctx.params.id});}));app.use(router.routes());app.listen();

Testing

Themod.ts exports an object namedtesting which contains some utilities fortesting oak middleware you might create. See theTesting with oak for moreinformation.


There are several modules that are directly adapted from other modules. Theyhave preserved their individual licenses and copyrights. All of the modules,including those directly adapted are licensed under the MIT License.

All additional work is copyright 2018 - 2025 the oak authors. All rightsreserved.

About

A middleware framework for handling HTTP with Deno, Node, Bun and Cloudflare Workers 🐿️ 🦕

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Languages


[8]ページ先頭

©2009-2025 Movatter.jp