es-fetch-api

    1.0.28 • Public • Published

    ES-Fetch-API

    中文 | English

    Very very very powerful, extensible http client for both node.js and browser.

    NPM npm GitHub package.json version GitHub file size in bytes GitHub issues GitHub pull requests codebeat badge CodeQL

    Why should you use ES-Fetch API?

    Still using axios? ES-Fetch-API creates sunny world for you.

    i. It's extremely light-weight and built on the native fetch API.

    Comparing to axios which is ~400kB, es-fetch-api is just ~6kB. Because es-fetch-api is designed for native fetch API compatible environments.

    References:

    1. fetch API on MDN
    2. fetch API on whatwg.org

    ii. Enables maximized readability, extensibility, maintainability and minimized complexity.

    1. The simplest example

    Expected request:

    GET http://yourdomain.com/api/v1/user?id=12345

    Using axios:

    import axios from 'axios'
    
    // unneccessarily indicate that 'http://yourdomain.com/api/v1' means baseURL
    const apiV1 = axios.create({ baseURL: 'http://yourdomain.com/api/v1' })
    
    // unneccessarily indicate that `/user` means url
    const getUser = async id => await apiV1.get({ url: `/user`, params: { id } })
    
    const response = await getUser(12345)

    Using es-fetch-api, great readability:

    import { getApi } from "es-fetch-api";
    import { query } from 'es-fetch-api/middelwares/query.js'
    
    // without mincing words
    const apiV1 = getApi('http://yourdomain.com/api/v1')
    
    const getUser = async id => await apiV1(`user`, query({ id }))
    
    const response = await getUser(12345)

    2. More complicated example (using built-in middlewares)

    Expected request:

    POST http://yourdomain.com/api/v1/user/
    Content-Type: application/json
    
    {"firstName":"Fred","lastName":"Flintstone"}
    

    Using axios:

    import axios from 'axios'
    
    const apiV1 = axios.create({ baseURL: 'http://yourdomain.com/api/v1' })
    
    // which format is used to post data?
    const createUser = async user => await apiV1.post(`/user`, user)
    
    const resposne = await createUser({
        firstName: 'Chun',
        lastName: 'Li'
    })

    Using es-fetch-api, better readability:

    import { getApi } from "es-fetch-api";
    import { json } from 'es-fetch-api/middelwares/body.js'
    import { POST } from 'es-fetch-api/middelwares/methods.js'
    
    const apiV1 = getApi('http://yourdomain.com/api/v1')
    
    // read what you see infomation losslessly 
    const createUser = async user => await apiV1(`user`, POST, json(user))
    
    const resposne = await createUser({
        firstName: 'Chun',
        lastName: 'Li'
    })

    3. Create custom middleware to extend your code while keeping better readability.

    Expected request:

    POST http://yourdomain.com/api/v1/user/
    Content-Type: application/json
    Auhorization: Token ********
    X-Timestamp: ##########
    
    {"firstName":"Fred","lastName":"Flintstone"}
    

    Using axios:

    import axios from 'axios'
    import { getToken } from 'token-helper'
    
    // easy to read? it's hard to understand they return headers.
    const useToken = async () => ({ 'Authorization': `Token ${await getToken()}` })
    const useTimestamp = async () => ({ 'X-Timestamp': Date.now() })
    
    const apiV1 = axios.create({ baseURL: 'http://yourdomain.com/api/v1' })
    
    // easy to read? Maybe or not, but too long winded to maintain.
    const createUser = async user => await apiV1.post({
        url: `/user`,
        data: user,
        headers: { ...await useToken(), ...await useTimestamp() }
    })
    
    const resposne = await createUser({
        firstName: 'Chun',
        lastName: 'Li'
    })

    Using es-fetch-api, better readability, better maintainability:

    import { getApi } from "es-fetch-api";
    import { json } from 'es-fetch-api/middelwares/body.js'
    import { POST } from 'es-fetch-api/middelwares/methods.js'
    import { getToken } from 'token-helper'
    
    // read what you see
    const useToken = async (ctx, next) => {
        ctx.header('Authorization', `Token ${await getToken()}`)
        return await next()
    }
    const useTimestamp = async (ctx, next) => {
        ctx.header('X-Timestamp', Date.now())
        return await next()
    }
    
    const apiV1 = getApi('http://yourdomain.com/api/v1')
    
    // read what you see infomation-losslessly 
    const createUser = async user => await apiV1(`user`, POST, json(user), useToken, useTimestamp)
    
    const resposne = await createUser({
        firstName: 'Chun',
        lastName: 'Li'
    })

    4. To use custom middlewares for every invocation.

    Using axios:

    import axios from 'axios'
    import { getToken } from 'token-helper'
    
    const useToken = async () => ({ 'Authorization': `Token ${await getToken()}` })
    const useTimestamp = async () => ({ 'X-Timestamp': Date.now() })
    
    // headers is static, especially the X-Timestamp. Easy to maintain? No!
    const apiV1 = axios.create({
        baseURL: 'http://yourdomain.com/api/v1',
        headers: { ...await useToken(), ...await useTimestamp() }
    })
    
    const createUser = async user => await apiV1.post({ url: `/user`, data: user, })
    const getUser = async id => await apiV1.get({ url: `/user`, params: { id } })

    Using es-fetch-api, better readability, better maintainability:

    import { getApi } from "es-fetch-api";
    import { json } from 'es-fetch-api/middelwares/body.js'
    import { POST } from 'es-fetch-api/middelwares/methods.js'
    import { getToken } from 'token-helper'
    
    const useToken = async (ctx, next) => {
        ctx.header('Authorization', `Token ${await getToken()}`)
        return await next()
    }
    const useTimestamp = async (ctx, next) => {
        ctx.header('X-Timestamp', Date.now())
        return await next()
    }
    
    // Just append the middlewares, so easy.
    const apiV1 = (...args) => getApi('http://yourdomain.com/api/v1')(...args, useToken, useTimestamp)
    
    const createUser = async user => await apiV1(`user`, POST, json(user))

    5. Process response.

    For instance with the getUser function.

    When the user exists, the response should be:

    Status: 200 OK
    Content-Type: application/json
    Body: {ok: true, data: {"firstName": "Chun", "lastName": "Li"}}
    

    When the user doesn't exist, the resposne should be:

    Status: 404 NotFound
    Content-Type: application/json
    Body: {ok: false, message: 'User doesn't exist.'}
    

    Using axios:

    import axios from 'axios'
    
    const apiV1 = axios.create({ baseURL: 'http://yourdomain.com/api/v1' })
    const getUser = async id => {
        try {
            const response = await apiV1.get({ url: `/user`, params: { id } })
            console.log(response.status, response.statusText)
            // So many data and error, make me confused...don't forget write the .data after the response :)
            const { data } = response.data
            return data
        } catch (error) {
            // which error is the error i want to use?
            console.log(error.response.data.message ?? error.message)
        }
    }

    Using es-fetch-api, great readability:

    import { getApi } from "es-fetch-api";
    import { query } from 'es-fetch-api/middelwares/query.js'
    
    const apiV1 = getApi('http://yourdomain.com/api/v1')
    
    const getUser = async id => {
        try {
            const response = await apiV1(`user`, query({ id }))
            console.log(response.status, response.statusText)
            const { ok, data, message } = await response.json() // read what you see
            if (!ok) throw { message }  // throw the error as you will
            return data
        } catch (error) {
            console.log(error.message)
        }
    }

    6. Process responses in a unified way

    Using axios:

    import axios from 'axios'
    
    // can you understand it? 
    // There seems no way to process errors in a unified way?
    const apiV1 = axios.create({ baseURL: 'http://yourdomain.com/api/v1' })
    
    const getOne = async config => {
        try {
            const resposne = await apiV1(config)
            console.log(response.status, response.statusText)
            const { ok, data, message } = response.data
            return data
        } catch (error) {
            console.log(error.response.data.message ?? error.message)
        }
    }

    Using es-fetch-api, great readability:

    import { getApi } from "es-fetch-api";
    import { query } from 'es-fetch-api/middelwares/query.js'
    
    const apiV1 = getApi('http://yourdomain.com/api/v1')
    
    const getOne = async (...args) => {
        try {
            const resposne = await apiV1(...args, useToken) // you can append custom middlewares here.
            console.log(response.status, response.statusText)
            const { ok, data, message } = await response.json() // read what you see
            if (!ok) throw { message }  // throw the error as you will
            return data
        } catch (error) {
            console.log(error.message ?? error)
        }
    }
    
    // getOne is the unified way to process every response. You could also write other logics such as getList
    // read what you see
    const getUser = async id => getOne(`user`, query({ id }))

    One word reason

    In es-fetch-api, each api invocation is a middlewares-chain, which means everything is extensible without introducing more complexity, no matter you want to process request and response in any unified way or case by case.

    Built-in middlewares

    method middleware

    This middleware is used to set HTTP method, it accepts a string parameter for method name to use. If an unsupported method name is used, an exception will be thrown.

    import { getApi } from "es-fetch-api";
    import { method } from 'es-fetch-api/middelwares/methods.js'
    
    const api = getApi('http://mydomain.com/api')
    
    const response = api('/', method('DELETE'))

    method aliases

    GET, POST, PUT, PATCH and DELETE, these are shorthands for each corresponding method.

    import { getApi } from "es-fetch-api";
    import { DELETE } from 'es-fetch-api/middelwares/methods.js'
    
    const api = getApi('http://mydomain.com/api')
    
    const response = api('/', DELETE)

    json middleware

    This middleware is used to declare the HTTP request body is an JSON object.

    It accepts an Object parameter to pass the body object in.

    When you use this middleware, the Content-Type: application/json header will be set automatically.

    import { getApi } from "es-fetch-api";
    import { json } from 'es-fetch-api/middelwares/body.js'
    import { POST } from 'es-fetch-api/middelwares/methods.js'
    
    const api = getApi('http://mydomain.com/api')
    
    const response = api('/', POST, json({ hello, world }))

    query middleware

    This middleware is used to declare the query string parameters of the request URL.

    It accepts two parameters.

    1. an Object, whose keys are the query parameter names and their corresponding value(s) are the query parameter values. If a value is an array with more than one element, then it will be an multi-value parameter.
    2. a Boolean, used to indicate whether each query parameter value should be appended to existed values. By Default, it's false.
    import { getApi } from "es-fetch-api";
    import { query } from 'es-fetch-api/middelwares/query.js'
    
    const api = getApi('http://mydomain.com/api?hello=1')
    
    api(query({ hello: 'world' })) // http://mydomain.com/api?hello=world
    api(query({ hello: 'world' }, true)) // http://mydomain.com/api?hello=1&hello=world
    api(query({ hello: [ 'Bing', 'Dwen', 'Dwen' ], world: '2022' })) // http://mydomain.com/api?hello=Bing&hello=Dwen&hello=Dwen&world=2022

    form middleware

    This middleware is used to declare the HTTP request body is form data.

    It accepts an Object parameter to pass form data in.

    When you use this middleware, the Content-Type: application/x-www-form-urlencoded header will be set automatically.

    import { getApi } from "es-fetch-api";
    import { form } from 'es-fetch-api/middelwares/body.js'
    
    const api = getApi('http://mydomain.com/api')
    
    api(POST, form({ hello: 'world' })) // hello=world

    file middleware

    This middleware is used to declare the HTTP request is uploading files.

    It accepts three parameters:

    1. the name of FormData field contains the file
    2. a File object
    3. give a filename, by default it's the original filename

    abortable middleware

    This middleware injects AbortController::signal into fetch, so that you can abort the request as you wish.

    You can use it to implement manual abort, timeout abort and so on.

    When the AbortController::abort() is invoked, an exception will be thrown.

    import { getApi } from "es-fetch-api";
    import { abortable } from 'es-fetch-api/middelwares/abortable.js'
    
    const api = getApi('http://mydomain.com/api')
    const controller = new AbortController()
    setTimeout(() => controller.abort(), 1000)
    api(abortable(controller))

    Middleware development

    Each middleware follow the same signature (Function and AsyncFunction both OK) and finally return next().

    const example = (ctx, next) => {
        // TODO: your logic
        return next()
    }

    or

    const example = async (ctx, next) => {
        // TODO: your logic
        return next()
    }

    More about the ctx

    The ctx is completely same as the Request, except the ctx exposes a helper method used to set request headers, see useToken middleware example in this document.

    License

    MIT

    Translations

    Install

    npm i es-fetch-api

    DownloadsWeekly Downloads

    7

    Version

    1.0.28

    License

    MIT

    Unpacked Size

    35.2 kB

    Total Files

    12

    Last publish

    Collaborators

    • lchrennew