Giter Site home page Giter Site logo

posva / mande Goto Github PK

View Code? Open in Web Editor NEW
1.1K 9.0 42.0 2.63 MB

<700 bytes convenient and modern wrapper around fetch with smart extensible defaults

Home Page: https://posva.net/mande/

License: MIT License

JavaScript 15.82% TypeScript 81.58% Shell 2.60%
fetch promise axios api

mande's Introduction

mande build status npm package coverage thanks

Simple, light and extensible wrapper around fetch with smart defaults

Requires fetch support.

mande has better defaults to communicate with APIs using fetch, so instead of writing:

// creating a new user
fetch('/api/users', {
  method: 'POST',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Dio',
    password: 'irejectmyhumanityjojo',
  }),
})
  .then((response) => {
    if (response.status >= 200 && response.status < 300) {
      return response.json()
    }
    // reject if the response is not 2xx
    throw new Error(response.statusText)
  })
  .then((user) => {
    // ...
  })

You can write:

const users = mande('/api/users')

users
  .post({
    name: 'Dio',
    password: 'irejectmyhumanityjojo',
  })
  .then((user) => {
    // ...
  })

Installation

npm install mande
yarn add mande

Usage

Creating a small layer to communicate to your API:

// api/users
import { mande } from 'mande'

const users = mande('/api/users', usersApiOptions)

export function getUserById(id) {
  return users.get(id)
}

export function createUser(userData) {
  return users.post(userData)
}

Adding Authorization tokens:

// api/users
import { mande } from 'mande'

const todos = mande('/api/todos', todosApiOptions)

export function setToken(token) {
  // todos.options will be used for all requests
  todos.options.headers.Authorization = 'Bearer ' + token
}

export function clearToken() {
  delete todos.options.headers.Authorization
}

export function createTodo(todoData) {
  return todo.post(todoData)
}
// In a different file, setting the token whenever the login status changes. This depends on your frontend code, for instance, some libraries like Firebase provide this kind of callback but you could use a watcher on Vue.
onAuthChange((user) => {
  if (user) setToken(user.token)
  else clearToken()
})

You can also globally add default options to all mande instances:

import { defaults } from 'mande'

defaults.headers.Authorization = 'Bearer token'

To delete a header, pass null to the mande instance or the request:

const legacy = mande('/api/v1/data', {
  headers: {
    // override all requests
    'Content-Type': 'application/xml',
  },
})

// override only this request
legacy.post(new FormData(), {
  headers: {
    // overrides Accept: 'application/json' only for this request
    Accept: null,
    'Content-Type': null,
  },
})

TypeScript

All methods defined on a mande instance accept a type generic to type their return:

const todos = mande('/api/todos', globalOptions)

todos
  .get<{ text: string; id: number; isFinished: boolean }[]>()
  .then((todos) => {
    // todos is correctly typed
  })

SSR (and Nuxt in Universal mode)

To make Mande work on Server, make sure to provide a fetch polyfill and to use full URLs and not absolute URLs starting with /. For example, using node-fetch, you can do:

export const BASE_URL = process.server
  ? process.env.NODE_ENV !== 'production'
    ? 'http://localhost:3000'
    : 'https://example.com'
  : // on client, do not add the domain, so urls end up like `/api/something`
    ''

const fetchPolyfill = process.server ? require('node-fetch') : fetch
const contents = mande(BASE_URL + '/api', {}, fetchPolyfill)

Nuxt 2

Note: If you are doing SSR with authentication, Nuxt 3 hasn't been adapted yet. See #308.

When using with Nuxt and SSR, you must wrap exported functions so they automatically proxy cookies and headers on the server:

import { mande, nuxtWrap } from 'mande'
const fetchPolyfill = process.server ? require('node-fetch') : fetch
const users = mande(BASE_URL + '/api/users', {}, fetchPolyfill)

export const getUserById = nuxtWrap(users, (api, id: string) => api.get(id))

Make sure to add it as a buildModule as well:

// nuxt.config.js
module.exports = {
  buildModules: ['mande/nuxt'],
}

This prevents requests from accidentally sharing headers or bearer tokens.

TypeScript

Make sure to include mande/nuxt in your tsconfig.json:

{
  "types": ["@types/node", "@nuxt/types", "mande/nuxt"]
}

API

Most of the code can be discovered through the autocompletion but the API documentation is available at https://posva.net/mande/.

Cookbook

Timeout

You can timeout requests by using the native AbortSignal:

mande('/api').get('/users', { signal: AbortSignal.timeout(2000) })

This is supported by all modern browsers.

FormData

When passing Form Data, mande automatically removes the Content-Type header but you can manually set it if needed:

// directly pass it to the mande instance
const api = mande('/api/', { headers: { 'Content-Type': null } })
// or when creating the request
const formData = new FormData()
api.post(formData, {
  headers: { 'Content-Type': 'multipart/form-data' },
})

Most of the time you should let the browser set it for you.

Related

  • fetchival: part of the code was borrowed from it and the api is very similar
  • axios:

License

MIT

mande's People

Contributors

archidevil avatar dependabot-preview[bot] avatar dependabot[bot] avatar obumnwabude avatar posva avatar renovate-bot avatar renovate[bot] avatar szepeviktor avatar usersaurus avatar wizbit avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mande's Issues

FormData support

I have searched in the code but I think it is impossible to send correctly a request with a FormData. fetch sends it correctly if you pass no Content-Type header but with mande you can't delete this header in a specific request. Are there any solution to this problem?

How do I set base url?

Hi, I'm playing around with mande and pinia.

How do I set the base URL once? My base URL differs from where my front-end will be.

Adding a query key to the request body inserts thousands of query params to the URL

I'm passing a graphql query to mande like so:

import { print } from 'graphql';
import { mande, defaults } from 'mande';

// Auth headers code ...

const gql = mande(BASE_URL + API_VER);

export default (query = '', variables = {}) =>
  gql
    .post({
      query: print(query),
      variables,
    })
    .then((res) => res)
    .catch((error) => error);

However for some reason the request that is sent has thousands of parameters added to the url. Is this a conflict in naming? I can't change the convention because the request is consumed by Shopify.

This causes a preflight 414 error code, because the query params are far too long and then there is a resultant CORS issue which I think is a strange side affect error message which will be fixed once the params issue is sorted.

EDIT: I believe the issue is caused by this helper: https://posva.net/mande/mande.options.html. Can you please make it a less generic key, like queryString perhaps.

how can i post a formdata request?

i want to request a token from a fastapi backend,and the code below is ok.

fetch(
        loginBaseUrl,
        {
            method: 'post',
            body: 'username=admin&password=123',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }
    )

how can i do it with mande? the code below cann't work.

const login =mande(loginBaseUrl)
const data =  login.post(
'',
 'username=admin&password=123',
{
 headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
}
)
console.log(data)

Question: Anyway to get error message which is returned by server

Hi there,
i am trying to use mande.
Is there any way to catch the error is given from server.
For an example:

 try {
          const response = await registerApi.post({
            email: this.email,
            password: this.password,
            passwordConfirmed: this.passwordRepeat,
          })

          resolve(response)
        } catch (error) {
          console.log('ServerError', error)
          reject(error)
        }

registerApi is a mande instance. The server returns for me i this case 422 (Unprocessable Entity) and an error message.
So how can i show this error message? console.log('ServerError', error) shows ServerError Error: Unprocessable Entity.
Thank you in advance

Mande sets Content-Type: application/json for simple get requests.

When using for simple GET request, like this:
await api.get('group/' + groupId + '/channels')
Mande sets Content-Type header with unsafe value of application/json, which issues preflight request.
GET requests shouldn't have a body, so I think that header is unnecessary or at least optional.
Can you fix this?

Sending FormData does not update Content-Type header to multipart/form-data

Trying to send file to a backend, right now it is now possible without overriding defaults. Sending FormData using the following code does not set Content-Type header to the correct value. It is application/json instead of multipart/form-data; boundaries=....

  const element = input.value as unknown as HTMLInputElement
  const attachedFile = element.files![0]

  const formData = new FormData()
  formData.append('file', attachedFile)
  const api = apiAccessor(props.url)
  await api.post('', formData)

When content type is set forcefully to multipart/form-data not every backend server could interpret data correctly without boundaries=... set automatically by the browser.

The current workaround is to drop defaults from mande and send files w/o content type set, it seems.

Using without a baseUrl

Hello,

I'm using the mande lib (lead here from fantastic Pinia), and I have a issue when using the following.

Is there an option to determine if the get|post url is a full url.. i.e. no baseUrl.

Thanks,
Bob

Couldn't find module declaration types

Importing error โŒ

Hello, I just installed the package on my new vite + vue 3 project at vsCode, i'm using "typescript": "^5.1.3", the following error appeared:

Could not find a declaration file for module 'mande'. '/home/guille/code/vue/node_modules/mande/dist/mande.mjs' implicitly has an 'any' type.
  There are types at '/home/guille/code/vue/node_modules/mande/dist/mande.d.ts', but this result could not be resolved when respecting package.json "exports". The 'mande' library may need to update its package.json or typings.

image

Reproduccion repo ๐Ÿ“‚

https://github.com/Guilledll/mande-type-issue

Is this something I can fix updating some package or changing my tsconfig.json or is an error from the mande package?

Thanks in advance! ๐Ÿ™Œ

Nuxt3 Migration

Looks like there is something missing/incompatible with Nuxt3

 ERROR  Error compiling template:  {                                                                                                                                                                                                                         12:28:10
  src: '/Users/byron/git/BigBlue/apps/website-nuxt3/node_modules/mande/nuxt/plugin.js',
  fileName: 'mande.js',
  options: {
    callError: true,
    proxyHeadersIgnore: [
      'accept',
      'host',
      'cf-ray',
      'cf-connecting-ip',
      'content-length',
      'content-md5',
      'content-type'
    ]
  },
  filename: 'mande.js',
  dst: '/Users/byron/git/BigBlue/apps/website-nuxt3/.nuxt/mande.js'
}


 ERROR  Cannot start nuxt:  serialize is not defined```

No response with status code 204

When requesting a response and the status code is 204 no response is returned.

// login returns 204 if successful (uses headers to specify user)
api.post<Response>('login', {email, password}, {responseAs: 'response'}).then(response => {
  // response is null
})

How to use interceptors

I am seeing hints of interceptors, but I don't think they're published or used at all for that matter.

onRequest(_context: {

I would like to stringifyQuery() differently, basically all the query values that are arrays, are being sent as CSV but I would like to change that.

Thank you.

Set default BASE URL

Hello.

Am i able to define a global Base URL like the Access Token?
For Example: My API is on http;//localhost:8000 and i wanna fetch data in every component just with const users = mande(/users).

Is this possible?

Invalid response type

Consider the following code:

import { mande } from 'mande'
const api = mande('baseUrl');
const response = api.get('1');

In this case response will be Promise<Response>, but it should be Promise<any> if no type specified since responseAs is json by default.

Correct me if I am wrong.

Typescript version: 4.6.4
Mande version: 2.0.1

image

[Feature Request] Support JSON.stringify replacer function

Firstly, thank you for making mande and continuing to support it ๐Ÿ™‚ this is the best fetch library available. I really appreciate it.

My proposal here is to add a config option for dev's to pass a replacer function to the JSON.stringify that is used when making API calls. This would really help when resolving cyclic reference issues.

Example inspired from MDN:

const replacer = () => {
  const seen = new WeakSet();
  return (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) {
        return;
      }
      seen.add(value);
    }
    return value;
  };
};

JSON.stringify(circularReference, replacer());

Maybe we can set it during the mande initialization, so it would look something like:

import { mande } from 'mande'
const users = mande('/api/users',{replacer})

Not adding trailing '/' to request

const rpc = mande('/rpc/endpoint')

rpc.post({some_rpc_name, ...}) // -> mande will always add trailing `/`, so the request path change to `/rpc/endpoint/`

My use case is the server has only one endpoint to serve all the requests(not REST API), my current workaround was let the server to listen to /rpc/endpoint and /rpc/endpoint/ both.

May add a option to disable adding the trailing / to the request path?

Promise exception with 200 query

Hello,

I'm sorry to bother, but I try to use mande for a simple 3D printer monitoring status service.
This printer runs locally, so I cannot provide any public snippet, but I'll try to explain as mush as I can.

I'm building a vue.js app requesting an auth token. This is the way I did instanciate mande (from pinia):

import { mande } from "mande";

...

const api = mande(`http://${config.host}:${config.port}/api/v1/connect`, {
  mode: "no-cors",
});

...

const formData = new FormData();
formData.append("token", config.token);
api.options.body = formData;

...

api.post()
.then((response) => {
  console.log(response);
})
.catch((err) => {
  console.log(err);
})

I can't figure out why, even if the request seems legit and valid, returning a 200 status code and a json object, mande/fetch throws an exception

SCR-20220906-x07

Error: 
    _fetch mande.mjs:110
    promise callback*_fetch mande.mjs:103
    getToken connection.js:36
    wrapAction pinia.mjs:1359
    actionName pinia.mjs:917
    ConnectionStore connection.js:59
    setup App.vue:9
    callWithErrorHandling runtime-core.esm-bundler.js:155
    setupStatefulComponent runtime-core.esm-bundler.js:7186
    setupComponent runtime-core.esm-bundler.js:7141
    mountComponent runtime-core.esm-bundler.js:5493
    processComponent runtime-core.esm-bundler.js:5468
    patch runtime-core.esm-bundler.js:5058
    render2 runtime-core.esm-bundler.js:6229
    mount runtime-core.esm-bundler.js:4422
    mount runtime-dom.esm-bundler.js:1606
    <anonymous> main.ts:12
[connection.js:42:23](http://localhost:5173/src/stores/connection.js?t=1662500438085)

It may be a begginer error or something, but what did I miss ?

Dependency Dashboard

This issue provides visibility into Renovate updates and their statuses. Learn more

Awaiting Schedule

These updates are awaiting their schedule. Click on a checkbox to get an update now.

  • chore(deps): update all non-major dependencies (@microsoft/api-documenter, @microsoft/api-extractor, @size-limit/preset-small-lib, @types/jest, jest, node-fetch, rollup, size-limit)

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.


  • Check this box to trigger a request for Renovate to run again on this repository

Timeout support

hi,
thanks for cool library.
Do you plan any timeout feature like axios has? I would find it very handy to have in place, not having custom implementations.

Thanks

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.