Ky: Set headers after ky declared

Created on 8 Apr 2019  路  15Comments  路  Source: sindresorhus/ky

Hello,

I have a quick question, I tried different options.

I have a service clientWeb.service.js :

import ky   from 'ky';

function authHeader() {
    /* return authorization header with jwt token */
    let user = JSON.parse(localStorage.getItem('user'));

    if (user && user.token) {
        return { 'x-access-token': user.token };
    } else {
        return {};
    }
}

const clientWeb = ky.extend({
    prefixUrl: process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : 'https://xxx.xxx.xxx',
    headers: authHeader()
});

export { clientWeb }

By default, headers can be set to null if the user is not connected.
Once the user is connected, I want to set my headers directly to my clientWeb.
I tried several things but nothing worked.

Here is my authentication.module.js

async login({ commit }, { email, password }) {
            try {
                commit('loginRequest', { email });

                let user = await userService.login(email, password);

                commit('loginSuccess', user);

                console.log(user);
                let headers = { 'x-access-token': user.token };

                console.log(clientWeb);

                clientWeb.headers = headers;
                clientWeb['headers'] = headers;
                // clientWeb({
                    // headers: headers
                // });

                router.push('/dashboard');
            } catch (err) {
                console.log('error login failure: ' + err);
                commit('loginFailure', err);
            }
        },

All 15 comments

I fixed it by doing this in my clientWeb.service.js :
image

But I would like to know if there is an other way.

Edit:
This does not work:

options.header = {'x-access-token': user.token } 

You have to do

options.headers.set('x-access-token', user.token);

Sounds like maybe you are calling ky.extend() before the local storage value is set somehow. Could be a race condition.

You can definitely set headers directly on an extended Ky instance without a hook. If that's not working for you, a simple CodeSandbox to reproduce it would be useful

How can you set the headers directly on an extended Ky instance?

Because I tried to access to clientWeb and set the headers directly after user logged in but the typeof of clientWeb is a function.

import { clientWeb } from '@/_services';
....
clientWeb.headers = headers;
clientWeb['headers'] = headers;
// clientWeb({
  // headers: headers
// });

This is a vue project so when I'll have the time I'll make a quick CodeSandbox.

Use the headers option and pass it to .extend(). This is what the code in your original post was doing and it looks correct. So let's figure out why that didn't work for you, because it should have.

Could you confirm for me that you are able to see the x-access-token header using this code sample:

const clientWeb = ky.extend({
    prefixUrl: 'http://localhost:3000',
    headers: { 'x-access-token': 'foo' }
});
const main = async () => {
    await clientWeb.post('/bar');
};
main();

My question was not declaring a new ky instance with headers.
It's while running, 2 or 3 minutes after ky instanced, change the headers directly.
I've been able to do it by using beforeRequest hook.

Okay, great. Closing since this is solved. Happy to re-open if you have any ideas or further problems related to setting headers.

This a super common use case. Every big web app has a login. So you need to set a Authorization header _after_ the user logged in.
The only way to archive this with ky seems to be either the ugly hook way or to create a new ky instance. Both are way to cumbersome.

Can you show a code example of how you would make it less cumbersome? I'm struggling to imagine how we could make it any easier. You can define default headers on Ky instances, you can define headers directly on a request when you make the request, and you can intercept a request to add headers by using a hook. What other way are you looking for? Do you just want to be able to override Ky's settings without creating a new instance? I think using .extend() is a much better way to override settings, since it can validate them for you, etc. But if you really wanted to, perhaps we could support directly modifying Ky's settings.

Thanks for the response.

I would love a way to update an instance directly. Creating a new instance is not cumbersome by itself, but accessing that instance is. I can't just export the ky instance in that case, i would have to wrap it (maybe I am overlooking a simple solution here?).

Something like this would be nice:

// same as extend, but modifies existing instance
instance.update({ headers: { foo: 'bar' } });

In ES modules, export instance should work and do the correct thing even if you re-assign a new instance to that variable, because ES modules use live bindings. In CommonJS, you could achieve something similar by exporting an object where the instance is on a property, i.e. module.exports.instsnce = and then in the module that imports it, use foo.instance, for example (don't destructure it).

I personally think that is better than Ky inventing something new to achieve the same goal.

IMO, the hook method is the better way to set a new header on a special request.

Yeah, the recommended way to customize headers based on some state in your app is to use hooks (there are good examples in our documentation). Or on a per-request basis, i.e. client.get('/foo', { headers : { hello : 'world' } }).

However, if you really don't want to use those techniques, then here is how I would solve this in your app:

import ky from 'ky';

let client = ky.extend({
    // ... some config ...
});
const setClientConfig = (config) => {
    client = client.extend(config);
};

export {
    client,
    setClientConfig
};

In the above example, client is the Ky instance that you would use throughout your app. setClientConfig() is just a simple function that allows other modules to update the Ky configuration, by creating a new Ky instance and updating the client variable to reference it.

If you only need to create Ky instances within this module, then you don't even need setClientConfig(), as you can just directly assign to the client variable. Either way, any modules that import the client will see the updated value with the new configuration as soon as it is assigned, thanks to the live bindings feature of ES modules. As I mentioned in a previous comment, you can achieve something similar in CommonJS (Node).

Now, we could add an update() method to Ky to mutate the configuration instead of creating new instances here. The only practical benefit I see is that it would allow you to use const instead of a let. But the client variable is read-only when import'd no matter what, anyway, so the benefit is relatively small. However, I think the internal changes needed in Ky to do this might be kind of messy, due to the need to re-process and validate the options. Plus there's the maintenance burden of having more API surface. It doesn't seem worth it to me, given that the other solutions are already very good and simple.

Does this suit your needs? We can certainly keep discussing it, especially if there is more demand for update().

Great write up @sholladay . I did not know about ES modules live binding (or i forgot about it). This is good enough for me and I understand that there is bias towards not implementing a update() method. But I still think that it would help from a user perspective...

If we can't improve/change the API, maybe we can improve the docs? IMO Setting the Authorization headers is so common that it deserves its own section (under "Tips" in the readme?). Not sure if live binding & extending or the hook style would be the recommended way.

Just a suggestion though. Feel free to close this issue again :)

Having slept on this for a while, I'm thinking that part of my dislike for update() is how it treats Ky as a very stateful thing, whereas our current API tries to minimize that. Under the hood, there is actually a Ky class that is instantiated with new Ky(), but we don't expose that publicly. The constructor even returns a promise instead of a true instance. It's a little funky, but it does a good job at pretending to be functional as opposed to object-oriented.

I would be more okay with adding an update() method if we exposed Ky as an actual class in order to make its statefulness more straightforward and obvious. But I'm not convinced that is the right approach... I want Ky to be more functional, not less.

I'm going to close this for now since it sounds like we came up with a decent solution. Thanks for the good discussion, it's useful to see how people are using Ky. 馃檶

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sarneeh picture sarneeh  路  6Comments

popuguytheparrot picture popuguytheparrot  路  6Comments

sindresorhus picture sindresorhus  路  7Comments

davalapar picture davalapar  路  6Comments

jacob-fueled picture jacob-fueled  路  3Comments