Giter Site home page Giter Site logo

node-fetch-middleware's Introduction

Build Status npm version semantic-release

Async middleware for node-fetch

Elegant asynchronous middleware chaining around node-fetch calls. Yes, kind of yet another one. Motivation behind this started with deprecation of request and looking at alternatives I just couldn't find a plugin system that was reasonable.

A middleware is a function that accepts the first two arguments of fetch - url and init and a wrapped function for the next middleware call which itself has identical API as original fetch, and returns a promise of a Response, same as fetch. Middleware has an opportunity to modify or replace request arguments and the response. Here's a middleware:

const mw = async (url, init, next) => {
  // ... do anything you want with url and init
  const response = await next(url, init)
  // ... do whatever you want with response
  return response
}

and you can use it like this:

mw('https://www.google.com', null, fetch)

compose

Main tool shipped in this library is compose which chains multiple middleware together:

const {compose} = require('node-fetch-middleware')

const fetch = compose([mw1, mw2])

fetch('https://www.google.com').then(console.log) // compose will tack on the last fetch for you

I'm also shipping several middleware I find useful with this library:

json

Allows structured json request body and parses json output.

const {compose, json} = require('node-fetch-middleware')

compose([json])('http://localhost:8080/myapi', {
  method: 'POST', json: {foo: 'bar'}
}).then(response => console.log(response.parsed))

query

Structured query string dubiously missing from fetch.

compose([query])('http://localhost:8080/myapi', {query: {foo: 'bar'}})

reject

Throw exception on an unexpected response.

compose([reject(/* Can provide own test, default will throw for status >= 400 */)])('https://nowhere')

prefix

Prefix all request urls.

compose([prefix('https://www.google.com', /* Other options */)])('/foo')

bearer

Attach a bearer token to requests, allows for asynchronous provider

compose([bearer({provider: async () => 'token'})])('https://localhost:8080/myapi')

cache

Cache responses. Factory defaults will cache GET, HEAD, and OPTIONS responses with status codes under 400 for 30 seconds. See options for possibilities.

compose([cache()])('https://www.google.com')

node-fetch-middleware's People

Contributors

dependabot[bot] avatar lev-kuznetsov avatar pke avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

node-fetch-middleware's Issues

Why is `next` declared as optional in middleware?

can next ever be empty? Isn't it at least the original fetch implementation?

export declare type Middleware = (url: RequestInfo, init?: RequestInit, next?: (url: RequestInfo, init?: RequestInit) => Promise<Response>) => Promise<Response>;

edit: Ah I understand now. Since this type is used to define middlewares as well as the return type of compose this can't be optional. What if compose and Middleware would use different signatures?

I have changed middleware.d.ts to

export declare type Middleware = (url: RequestInfo, init: RequestInit, next: (url: RequestInfo, init?: RequestInit) => Promise<Response>) => Promise<Response>;

and compose.d.ts to

export declare function compose(middleware: Middleware[]): (url: RequestInfo, init?: RequestInit) => Promise<Response>;

This gives nice type-safety in middlwares, however it complains that parsed does not exist on Response

const fetchToken = compose([log(), json])

async function token() {
  const { parsed: { access_token } } = await fetchToken("https://foobar.auth0.com/", {
  ...
}

This information is lost in compose I guess. Maybe helping compose out a bit with a combined Response type could help?

export declare function compose<T extends Response>(middleware: Middleware[]): (url: RequestInfo, init?: RequestInit) => Promise<T>;

Then then json middleware would have to export its extension of the Response type? Does this make sense?

// Pseudo code
const fetchToken = compose<Response & ParsedJson>([log(), json])

edit: Looks like this works:

// compose.d.ts
import { RequestInfo, RequestInit, Response } from 'node-fetch';
import { Middleware } from './middleware';
export declare function compose<RequestInitT extends RequestInit, ResponseT extends Response>(middleware: Middleware[]): (url: RequestInfo, init?: RequestInitT) => Promise<ResponseT>;

// middleware.d.ts
import { RequestInfo, RequestInit, Response } from 'node-fetch';
export declare type Middleware = (url: RequestInfo, init: RequestInit, next: (url: RequestInfo, init?: RequestInit) => Promise<Response>) => Promise<Response>;

// json.d.ts
import { Middleware } from './middleware';
declare module 'node-fetch' {
    interface Body {
        parsed?: any;
    }
    interface RequestInit {
        json?: any;
    }
}

export interface JsonInit {
  json?: any
}

export interface JsonResponse {
  parsed?: any
}

export declare const json: Middleware;

// main.ts
import { RequestInit, Response } from "node-fetch"
import { compose, json, bearer, Middleware, JsonInit, JsonResponse } from "node-fetch-middleware"

const fetchToken = compose<RequestInit & JsonInit, Response & JsonResponse>([log(), json])

add retry-after middleware

We could add a middleware that reacts to error (5xx) status codes (or optionally always, regardless of error code) and evaluates a Retry-After response header to retry the request.

retry(shouldRetry: (Response response) => boolean = ({ statusCode }) => statusCode >= 500))

Implement bearer token refresh

I wonder if it would be possible to implement a bearer token refresh middleware that in itself would benefit from the other registered middlewares like json response parsing.

I think its rather difficult as from within the bearer middleware we do not have access to the outer fetch to perform our refresh request (and get its response parsed into response.parsed) or do you have an idea @lev-kuznetsov?

json middleware should only try to parse json content-type

the mw should inspec the content type and only attempt to parse into JSON if the content type (partially) includes json.

Would love to create all those PRs for the features/bugfixes but I can't get the tests to run.
Maybe we also want to change the tests to jest and use msw (Mock Service Worker) to simulate the request flows (bearer, retry)?

next defined as optional in Middleware causes ts compiler warnings

Looks like this is a duplicate of #2, I just realised. However, since I ran into this a second time now, maybe its worth reiterating.

export declare type Middleware = (url: RequestInfo, init?: RequestInit, next?: (url: RequestInfo, init?: RequestInit) => Promise<Response>) => Promise<Response>;
since next is defined optional it always causes compile warnings in TS projects that implement custom mw.
Its optional because it follows another optional param. Maybe make init not optional?

export declare type Middleware = (url: RequestInfo, init: RequestInit|undefined, next: (url: RequestInfo, init?: RequestInit) => Promise<Response>) => Promise<Response>;

edit: hmmm, this will not work when const fetch = compose is used. Then it would expect next on every fetch call
This could be fixed by using

import fetch from "node-fetch"
export declare function compose(middleware: Middleware[]): typeof fetch;

However this all does not solve the problem, that the json response modification is not known to downstream promise handlers. They don't know response.parsed

Remove lodash dependency for merge

I think all merge calls can be replaced with ES destructuring and splashing composition.

merge(init, {headers: {'Authorization': `Bearer ${token}`}})

I wanted to create a PR but all the current tests don't run:

01:50 $ npm t

> [email protected] test /var/storage/dev/projects/oss/node-fetch-middleware
> mocha


/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/ts-node/src/index.ts:293
    return new TSError(diagnosticText, diagnosticCodes)
           ^
TSError: ⨯ Unable to compile TypeScript:
src/cache.ts:6:7 - error TS2395: Individual declarations in merged declaration 'cache' must be all exported or all local.

6 const cache = new NodeCache()
        ~~~~~
src/cache.ts:21:17 - error TS2395: Individual declarations in merged declaration 'cache' must be all exported or all local.

21 export function cache(options: CacheOptions = {}): Middleware {
                   ~~~~~
src/cache.ts:25:29 - error TS2339: Property 'get' does not exist on type '(options?: CacheOptions) => Middleware'.

25     get: async key => cache.get(key) as Response,
                               ~~~
src/cache.ts:26:47 - error TS2339: Property 'set' does not exist on type '(options?: CacheOptions) => Middleware'.

26     set: async (key, response, ttl) => {cache.set(key, response, ttl)}
                                                 ~~~

    at createTSError (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/ts-node/src/index.ts:293:12)
    at reportTSError (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/ts-node/src/index.ts:297:19)
    at getOutput (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/ts-node/src/index.ts:399:34)
    at Object.compile (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/ts-node/src/index.ts:457:32)
    at Module.m._compile (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/ts-node/src/index.ts:536:43)
    at Module._extensions..js (internal/modules/cjs/loader.js:962:10)
    at Object.require.extensions.<computed> [as .ts] (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/ts-node/src/index.ts:539:12)
    at Module.load (internal/modules/cjs/loader.js:798:32)
    at Function.Module._load (internal/modules/cjs/loader.js:711:12)
    at Module.require (internal/modules/cjs/loader.js:838:19)
    at require (internal/modules/cjs/helpers.js:74:18)
    at Object.<anonymous> (/var/storage/dev/projects/oss/node-fetch-middleware/src/index.ts:2:1)
    at Module._compile (internal/modules/cjs/loader.js:945:30)
    at Module.m._compile (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/ts-node/src/index.ts:536:23)
    at Module._extensions..js (internal/modules/cjs/loader.js:962:10)
    at Object.require.extensions.<computed> [as .ts] (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/ts-node/src/index.ts:539:12)
    at Module.load (internal/modules/cjs/loader.js:798:32)
    at Function.Module._load (internal/modules/cjs/loader.js:711:12)
    at Module.require (internal/modules/cjs/loader.js:838:19)
    at require (internal/modules/cjs/helpers.js:74:18)
    at Object.<anonymous> (/var/storage/dev/projects/oss/node-fetch-middleware/test/bearer-test.ts:1:1)
    at Module._compile (internal/modules/cjs/loader.js:945:30)
    at Module.m._compile (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/ts-node/src/index.ts:536:23)
    at Module._extensions..js (internal/modules/cjs/loader.js:962:10)
    at Object.require.extensions.<computed> [as .ts] (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/ts-node/src/index.ts:539:12)
    at Module.load (internal/modules/cjs/loader.js:798:32)
    at Function.Module._load (internal/modules/cjs/loader.js:711:12)
    at Module.require (internal/modules/cjs/loader.js:838:19)
    at require (internal/modules/cjs/helpers.js:74:18)
    at /var/storage/dev/projects/oss/node-fetch-middleware/node_modules/mocha/lib/mocha.js:334:36
    at Array.forEach (<anonymous>)
    at Mocha.loadFiles (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/mocha/lib/mocha.js:331:14)
    at Mocha.run (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/mocha/lib/mocha.js:809:10)
    at Object.exports.singleRun (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/mocha/lib/cli/run-helpers.js:108:16)
    at exports.runMocha (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/mocha/lib/cli/run-helpers.js:142:13)
    at Object.exports.handler (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/mocha/lib/cli/run.js:292:3)
    at Object.runCommand (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/yargs/lib/command.js:242:26)
    at Object.parseArgs [as _parseArgs] (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/yargs/yargs.js:1087:28)
    at Object.parse (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/yargs/yargs.js:566:25)
    at Object.exports.main (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/mocha/lib/cli/cli.js:68:6)
    at Object.<anonymous> (/var/storage/dev/projects/oss/node-fetch-middleware/node_modules/mocha/bin/mocha:164:29)
    at Module._compile (internal/modules/cjs/loader.js:945:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:962:10)
    at Module.load (internal/modules/cjs/loader.js:798:32)
    at Function.Module._load (internal/modules/cjs/loader.js:711:12)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1014:10)
    at internal/main/run_main_module.js:17:11
npm ERR! Test failed.  See above for more details.
✘-1 ~/dev-oss/node-fetch-middleware [master|✔]

Maybe we could also switch tests to jest and get --coverage for free?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.