431

In my node.js application I did an npm install btoa-atob so that I could use the btoa() and atob() functions which are native in client-side javascript but for some reason weren't included in node. The new directory showed up in my node_modules folder, which itself is in root alongside app.js. Then I made sure to add btoa-atob as a dependency in my package.json file which is in root.

However, for some reason, it still will not work.

console.log(btoa("Hello World!"));

^ should output "SGVsbG8gV29ybGQh" to the console, but instead, I get the error:

btoa is not defined.

Did I not do the install properly? What did I overlook?

Penny Liu
  • 15,447
  • 5
  • 79
  • 98
Joey
  • 10,504
  • 16
  • 39
  • 54

13 Answers13

915

The 'btoa-atob' module does not export a programmatic interface, it only provides command line utilities.

If you need to convert to Base64 you could do so using Buffer:

console.log(Buffer.from('Hello World!').toString('base64'));

Reverse (assuming the content you're decoding is a utf8 string):

console.log(Buffer.from(b64Encoded, 'base64').toString());

Note: prior to Node v4, use new Buffer rather than Buffer.from.

mscdex
  • 104,356
  • 15
  • 192
  • 153
132

The solutions posted here don't work in non-ascii characters (i.e. if you plan to exchange base64 between Node.js and a browser). In order to make it work you have to mark the input text as 'binary'.

Buffer.from('Hélló wórld!!', 'binary').toString('base64')

This gives you SOlsbPMgd/NybGQhIQ==. If you make atob('SOlsbPMgd/NybGQhIQ==') in a browser it will decode it in the right way. It will do it right also in Node.js via:

Buffer.from('SOlsbPMgd/NybGQhIQ==', 'base64').toString('binary')

If you don't do the "binary part", you will decode wrongly the special chars.

I got it from the implementation of the btoa npm package:

Iván Alegre
  • 1,818
  • 1
  • 14
  • 12
  • **Iván Alegre** Just don't use 'binary' encoding. If you do `Buffer.from('Hélló wórld!!').toString('base64')` — it will give you `SOlsbPMgd/NybGQhIQ==` which can be converted to non-ascii string back properly. – TotalAMD Jun 18 '19 at 10:53
  • 2
    @TotalAMD it won't work exchanging base64 from Node.js to browser or viceversa – Iván Alegre Jun 19 '19 at 08:29
  • @IvánAlegre Just checked a) In Chrome 75: `atob(btoa("Hélló wórld!!")) === "Hélló wórld!!"` b) In Node 10: `Buffer.from('Hélló wórld!!').toString('base64') === 'SMOpbGzDsyB3w7NybGQhIQ=='` and `Buffer.from('SMOpbGzDsyB3w7NybGQhIQ==', 'base64').toString() === 'Hélló wórld!!'` c) Node's `Buffer.from()` can read even if you remove trailing '=', but you can always write simple function to complete encoded string with trailing '='. – TotalAMD Jun 23 '19 at 12:36
  • 7
    You are comparing encoding in base64 and decoding it in the same platform. Chrome to Chrome and Node to Node. If you encode it in Node 10 without binary, it will give `SMOpbGzDsyB3w7NybGQhIQ==`. If you decode this in a browser it will give you `Hélló wórld!!`. The binary is perfect to ensure cross platform compatibility. – Iván Alegre Jun 24 '19 at 08:02
  • 1
    Your answer in a function: `function btoa(str){return Buffer.from(str, 'binary').toString('base64');}` `function atob(str){return Buffer.from(str, 'base64').toString('binary');}` – SwiftNinjaPro Nov 13 '19 at 22:07
  • @IvánAlegre `binary` is just an alias for `latin1` (see [here](https://nodejs.org/api/buffer.html#buffers-and-character-encodings)), which strips out any non-latin character (Cyrillic, Greek, Chinese, and a ton of other characters). `Buffer.from('Σ', 'binary').toString('base64')'is 'ow=='`, `atob('ow==')` is `£` – user2340612 Jul 14 '23 at 18:07
35

My team ran into this problem when using Node with React Native and PouchDB. Here is how we solved it...

NPM install buffer:

$ npm install --save buffer

Ensure Buffer, btoa, and atob are loaded as a globals:

global.Buffer = global.Buffer || require('buffer').Buffer;

if (typeof btoa === 'undefined') {
  global.btoa = function (str) {
    return new Buffer(str, 'binary').toString('base64');
  };
}

if (typeof atob === 'undefined') {
  global.atob = function (b64Encoded) {
    return new Buffer(b64Encoded, 'base64').toString('binary');
  };
}
Michael_Scharf
  • 33,154
  • 22
  • 74
  • 95
PaulMest
  • 12,925
  • 7
  • 53
  • 50
  • 2
    The command new Buffer() in your code gives the following error in newer versions of node: [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead. – Rodrigo De Almeida Siqueira Jun 13 '19 at 13:14
  • 3
    @RodrigoDeAlmeidaSiqueira, you can use Buffer.from() to fix the warning :) – jm' Aug 09 '20 at 00:52
20
export const universalBtoa = str => {
  try {
    return btoa(str);
  } catch (err) {
    return Buffer.from(str).toString('base64');
  }
};

export const universalAtob = b64Encoded => {
  try {
    return atob(b64Encoded);
  } catch (err) {
    return Buffer.from(b64Encoded, 'base64').toString();
  }
};
Ali Eslamifard
  • 565
  • 3
  • 11
  • 1
    universalAtob didn't work for me until I changed it to `Buffer.from(b64Encoded, 'base64').toString('binary');` like https://stackoverflow.com/a/47890385/470749 – Ryan Oct 25 '21 at 15:17
16

I found that although the shims from answers above worked, they did not match the behaviour of desktop browsers' implementations of btoa() and atob():

const btoa = function(str){ return Buffer.from(str).toString('base64'); }
// returns "4pyT", yet in desktop Chrome would throw an error.
btoa('✓');
// returns "fsO1w6bCvA==", yet in desktop Chrome would return "fvXmvA=="
btoa(String.fromCharCode.apply(null, new Uint8Array([0x7e, 0xf5, 0xe6, 0xbc])));

As it turns out, Buffer instances represent/interpret strings encoded in UTF-8 by default. By contrast, in desktop Chrome, you can't even input a string that contains characters outside of the latin1 range into btoa(), as it will throw an exception: Uncaught DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.

Therefore, you need to explicitly set the encoding type to latin1 in order for your Node.js shim to match the encoding type of desktop Chrome:

const btoaLatin1 = function(str) { return Buffer.from(str, 'latin1').toString('base64'); }
const atobLatin1 = function(b64Encoded) {return Buffer.from(b64Encoded, 'base64').toString('latin1');}

const btoaUTF8 = function(str) { return Buffer.from(str, 'utf8').toString('base64'); }
const atobUTF8 = function(b64Encoded) {return Buffer.from(b64Encoded, 'base64').toString('utf8');}

btoaLatin1('✓'); // returns "Ew==" (would be preferable for it to throw error because this is undecodable)
atobLatin1(btoa('✓')); // returns "\u0019" (END OF MEDIUM)

btoaUTF8('✓'); // returns "4pyT"
atobUTF8(btoa('✓')); // returns "✓"

// returns "fvXmvA==", just like desktop Chrome
btoaLatin1(String.fromCharCode.apply(null, new Uint8Array([0x7e, 0xf5, 0xe6, 0xbc])));
// returns "fsO1w6bCvA=="
btoaUTF8(String.fromCharCode.apply(null, new Uint8Array([0x7e, 0xf5, 0xe6, 0xbc])));
Jamie Birch
  • 5,839
  • 1
  • 46
  • 60
  • in node v0.12.2 there is not a Buffer.from function – Zibri Nov 11 '18 at 12:44
  • 1
    @Zibri Node v0.12.2 is ancient and reached end-of-life [two years ago](https://github.com/nodejs/Release#end-of-life-releases). `Buffer.from()` is the [recommended way](https://nodejs.org/en/docs/guides/buffer-constructor-deprecation/) to use the Buffer API due to security reasons (although that link will clarify alternatives to `Buffer.from()` that may apply for Node v0.12.2). – Jamie Birch Nov 11 '18 at 13:18
  • I understand that, but on an embedded device I have that version. – Zibri Nov 28 '18 at 16:49
  • I am running my code in Atom using the script package https://github.com/rgbkrk/atom-script that is an old implementation of node. In other words, it also needs an implementation for btoa, while it can't cope with Buffer.from(). – Shrimpy Dec 08 '19 at 05:48
  • I upvoted this because it's closest to correct. The browser atob / btoa functions specifically need character code points in the range of 0-255. Latin1 is in this range, but doesn't use every character in this range. The point of btoa and atob is to encode / decode actual binary data for transport over a text channel. If you're encoding / decoding text, atob and btoa are probably unrelated to what you're doing. – Ryan Hanekamp Jul 29 '20 at 15:08
12

I have a code shared between server and client and I needed an implementation of btoa inside it. I tried doing something like:

const btoaImplementation =  btoa || (str => Buffer.from(str).toString('base64'));

but the Server would crush with:

ReferenceError: btoa is not defined

while Buffer is not defined on the client.

I couldn't check window.btoa (it's a shared code, remember?)

So I ended up with this implementation:

const btoaImplementation = str => {
    try {
        return btoa(str);
    } catch(err) {
        return Buffer.from(str).toString('base64')
    }
};
Alexander
  • 7,484
  • 4
  • 51
  • 65
8

I was able to use btoa for binary data to base 64 string conversion using below npm package: https://www.npmjs.com/package/btoa

As described in their documentation, I did below steps in node JS application:

  1. Install => npm install --save btoa
  2. Declare at top => const btoa = require('btoa');
  3. Use => const b64 = btoa("stringToEncode");
Saket Kumar
  • 4,363
  • 4
  • 32
  • 55
8

Anybody looking to decode:

let decoded = Buffer.from(<encoded string>, 'base64').toString()

Because I came here looking for decoding, ended up figuring it out from an answer here.

Greg Sadetsky
  • 4,863
  • 1
  • 38
  • 48
Rutwick Gangurde
  • 4,772
  • 11
  • 53
  • 87
6

Here's a concise universal solution for base64 encoding:

const nodeBtoa = (b) => Buffer.from(b).toString('base64');
export const base64encode = typeof btoa !== 'undefined' ? btoa : nodeBtoa;
Luke Taylor
  • 8,631
  • 8
  • 54
  • 92
5

Same problem with the 'script' plugin in the Atom editor, which is an old version of node, not having btoa(), nor atob(), nor does it support the Buffer datatype. Following code does the trick:

var Base64 = new function() {
  var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
  this.encode = function(input) {
    var output = "";
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    var i = 0;
    input = Base64._utf8_encode(input);
    while (i < input.length) {
      chr1 = input.charCodeAt(i++);
      chr2 = input.charCodeAt(i++);
      chr3 = input.charCodeAt(i++);
      enc1 = chr1 >> 2;
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
      enc4 = chr3 & 63;
      if (isNaN(chr2)) {
        enc3 = enc4 = 64;
      } else if (isNaN(chr3)) {
        enc4 = 64;
      }
      output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
    }
    return output;
  }

  this.decode = function(input) {
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;
    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
    while (i < input.length) {
      enc1 = keyStr.indexOf(input.charAt(i++));
      enc2 = keyStr.indexOf(input.charAt(i++));
      enc3 = keyStr.indexOf(input.charAt(i++));
      enc4 = keyStr.indexOf(input.charAt(i++));
      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;
      output = output + String.fromCharCode(chr1);
      if (enc3 != 64) {
        output = output + String.fromCharCode(chr2);
      }
      if (enc4 != 64) {
        output = output + String.fromCharCode(chr3);
      }
    }
    output = Base64._utf8_decode(output);
    return output;
  }

  this._utf8_encode = function(string) {
    string = string.replace(/\r\n/g, "\n");
    var utftext = "";
    for (var n = 0; n < string.length; n++) {
      var c = string.charCodeAt(n);
      if (c < 128) {
        utftext += String.fromCharCode(c);
      } else if ((c > 127) && (c < 2048)) {
        utftext += String.fromCharCode((c >> 6) | 192);
        utftext += String.fromCharCode((c & 63) | 128);
      } else {
        utftext += String.fromCharCode((c >> 12) | 224);
        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
        utftext += String.fromCharCode((c & 63) | 128);
      }
    }
    return utftext;
  }

  this._utf8_decode = function(utftext) {
    var string = "";
    var i = 0;
    var c = 0,
      c1 = 0,
      c2 = 0,
      c3 = 0;
    while (i < utftext.length) {
      c = utftext.charCodeAt(i);
      if (c < 128) {
        string += String.fromCharCode(c);
        i++;
      } else if ((c > 191) && (c < 224)) {
        c2 = utftext.charCodeAt(i + 1);
        string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
        i += 2;
      } else {
        c2 = utftext.charCodeAt(i + 1);
        c3 = utftext.charCodeAt(i + 2);
        string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
        i += 3;
      }
    }
    return string;
  }
}()

var btoa = Base64.encode;
var atob = Base64.decode;

console.log("btoa('A') = " + btoa('A'));
console.log("btoa('QQ==') = " + atob('QQ=='));
console.log("btoa('B') = " + btoa('B'));
console.log("btoa('Qg==') = " + atob('Qg=='));
Shrimpy
  • 174
  • 3
  • 5
  • This works thanks. In my case, I am using ChakraEngine which doesn't seem to support atob. – Water Jun 05 '20 at 03:58
3

If you end up here, looking for atob is not defined solution (like me). Try upgrade your nodejs version - it helps me

lojza
  • 1,823
  • 2
  • 13
  • 23
  • seems to work fine with the 16.x branch (have not tested it with prior versions) – Matt Jun 18 '22 at 11:51
  • `atob()` I think is usually defined under `window`, so in a Node environment without window object, that is not defined, and you must use `Buffer` instead. But Node v16 added the compatibility. Tested on v14.19.3 and 16.15.1 – David Min Sep 16 '22 at 11:20
1

I understand this is a discussion point for a node application, but in the interest of universal JavaScript applications running on a node server, which is how I arrived at this post, I have been researching this for a universal / isomorphic react app I have been building, and the package abab worked for me. In fact it was the only solution I could find that worked, rather than using the Buffer method also mentioned (I had typescript issues).

(This package is used by jsdom, which in turn is used by the window package.)

Getting back to my point; based on this, perhaps if this functionality is already written as an npm package like the one you mentioned, and has it's own algorithm based on W3 spec, you could install and use the abab package rather than writing you own function that may or may not be accurate based on encoding.

---EDIT---

I started having weird issues today with encoding (not sure why it's started happening now) with package abab. It seems to encode correctly most of the time, but sometimes on front end it encodes incorrectly. Spent a long time trying to debug, but switched to package base-64 as recommended, and it worked straight away. Definitely seemed to be down to the base64 algorithm of abab.

Phil Gibbins
  • 492
  • 5
  • 13
0

Unlike in web browsers (where btoa() is a global function), in Node.js versions below 16 the btoa() function is not available in the global namespace. For this reason, in Node.js versions before 16, when you try to access btoa() function in the global namespace, it throws a "btoa is not defined" error.

Regardless though, btoa() is marked as "legacy" in the Node.js documentation, and it is not recommended to be used for base64-encoding a string as it cannot handle characters beyond the ASCII/Latin-1 character set. As mentioned in other answers, you should use Buffer.from() instead, for example, like so:

Buffer.from('Hello World!').toString('base64');

Wrote a blog post about it for those who wish to learn more.

designcise
  • 4,204
  • 1
  • 17
  • 13