Outils·6 min de lecture·Par Solingo

Ethers.js vs Web3.js — Which Library Should You Use?

Ethers.js and Web3.js are the two most popular JavaScript libraries for Ethereum. Compare their features, API design, bundle size, and performance to choose the right one for your project.

# Ethers.js vs Web3.js — Which Library Should You Use?

When building Ethereum dApps, you need a JavaScript library to interact with the blockchain. The two dominant choices are Ethers.js and Web3.js. Both enable wallet connections, contract interactions, and transaction management — but they differ significantly in design philosophy, bundle size, and developer experience. This guide compares both libraries to help you choose the right one for your project.

Quick Comparison Table

| Feature | Ethers.js | Web3.js |

|---------|-----------|---------|

| Bundle size | 116 KB (minified) | 600+ KB (minified) |

| API design | Modern, clean, TypeScript-first | Legacy, callback-heavy |

| TypeScript | Full native support | Added via @types/web3 |

| Maintenance | Actively maintained | Slower updates |

| Documentation | Excellent | Good but scattered |

| Learning curve | Moderate | Steeper |

| Provider management | Simple (BrowserProvider) | Complex (Web3.providers) |

| ENS support | Built-in | Via separate package |

| BigNumber | Native BigInt + custom BigNumber | Custom BigNumber |

| Community | Growing fast | Established |

| Best for | New projects, production dApps | Legacy projects, Web3.js v4 |

Installation

Ethers.js v6

npm install ethers
import { BrowserProvider, Contract } from 'ethers';

Web3.js v4

npm install web3
import { Web3 } from 'web3';

Connecting to Ethereum

Ethers.js

import { BrowserProvider } from 'ethers';

// Connect to MetaMask

const provider = new BrowserProvider(window.ethereum);

const signer = await provider.getSigner();

const address = await signer.getAddress();

console.log('Connected:', address);

Clean separation:

  • Provider: Read-only access to blockchain
  • Signer: Write access (transactions, signing)

Web3.js

import { Web3 } from 'web3';

// Connect to MetaMask

const web3 = new Web3(window.ethereum);

const accounts = await web3.eth.requestAccounts();

const address = accounts[0];

console.log('Connected:', address);

Less explicit separation between read/write operations.

Reading Blockchain Data

Ethers.js

// Get balance

const balance = await provider.getBalance(address);

console.log('Balance:', ethers.formatEther(balance), 'ETH');

// Get block number

const blockNumber = await provider.getBlockNumber();

// Get gas price

const feeData = await provider.getFeeData();

console.log('Gas price:', ethers.formatUnits(feeData.gasPrice, 'gwei'), 'gwei');

// Get transaction

const tx = await provider.getTransaction(txHash);

Web3.js

// Get balance

const balance = await web3.eth.getBalance(address);

console.log('Balance:', web3.utils.fromWei(balance, 'ether'), 'ETH');

// Get block number

const blockNumber = await web3.eth.getBlockNumber();

// Get gas price

const gasPrice = await web3.eth.getGasPrice();

console.log('Gas price:', web3.utils.fromWei(gasPrice, 'gwei'), 'gwei');

// Get transaction

const tx = await web3.eth.getTransaction(txHash);

Very similar API, but different utility function namespaces.

Sending Transactions

Ethers.js

const signer = await provider.getSigner();

const tx = await signer.sendTransaction({

to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',

value: ethers.parseEther('0.1')

});

console.log('Transaction hash:', tx.hash);

// Wait for confirmation

const receipt = await tx.wait();

console.log('Confirmed in block:', receipt.blockNumber);

tx.wait() returns a promise that resolves when the transaction is mined.

Web3.js

const accounts = await web3.eth.getAccounts();

const tx = await web3.eth.sendTransaction({

from: accounts[0],

to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',

value: web3.utils.toWei('0.1', 'ether')

});

console.log('Transaction hash:', tx.transactionHash);

console.log('Confirmed in block:', tx.blockNumber);

Web3.js returns the receipt directly (no separate wait() call).

Contract Interaction

Ethers.js

import { Contract } from 'ethers';

const abi = [

"function balanceOf(address owner) view returns (uint256)",

"function transfer(address to, uint256 amount) returns (bool)"

];

const contract = new Contract(contractAddress, abi, signer);

// Read function (no gas)

const balance = await contract.balanceOf(address);

console.log('Balance:', ethers.formatUnits(balance, 18));

// Write function (costs gas)

const tx = await contract.transfer('0x...', ethers.parseUnits('10', 18));

await tx.wait();

console.log('Transfer complete');

Human-readable ABI — you can define interfaces as strings instead of JSON.

Web3.js

const abi = [

{

"name": "balanceOf",

"type": "function",

"inputs": [{"name": "owner", "type": "address"}],

"outputs": [{"name": "", "type": "uint256"}],

"stateMutability": "view"

},

{

"name": "transfer",

"type": "function",

"inputs": [

{"name": "to", "type": "address"},

{"name": "amount", "type": "uint256"}

],

"outputs": [{"name": "", "type": "bool"}]

}

];

const contract = new web3.eth.Contract(abi, contractAddress);

// Read function

const balance = await contract.methods.balanceOf(address).call();

console.log('Balance:', web3.utils.fromWei(balance, 'ether'));

// Write function

const accounts = await web3.eth.getAccounts();

await contract.methods.transfer('0x...', web3.utils.toWei('10', 'ether'))

.send({ from: accounts[0] });

JSON ABI required — more verbose, but compatible with all tools.

Event Listening

Ethers.js

// Listen to Transfer events

contract.on('Transfer', (from, to, amount, event) => {

console.log(Transfer from ${from} to ${to}: ${ethers.formatUnits(amount, 18)} tokens);

});

// Query past events

const filter = contract.filters.Transfer(null, myAddress);

const events = await contract.queryFilter(filter, startBlock, endBlock);

Clean event API with filters.

Web3.js

// Listen to Transfer events

contract.events.Transfer()

.on('data', (event) => {

console.log(Transfer from ${event.returnValues.from} to ${event.returnValues.to});

});

// Query past events

const events = await contract.getPastEvents('Transfer', {

filter: { to: myAddress },

fromBlock: startBlock,

toBlock: endBlock

});

More verbose event handling.

BigNumber Handling

Ethers.js

Ethers.js v6 uses native JavaScript BigInt for most operations, with a custom BigNumber class for advanced needs:

const amount = ethers.parseEther('1.5'); // BigInt: 1500000000000000000n

const formatted = ethers.formatEther(amount); // "1.5"

// Math operations

const doubled = amount * 2n; // Native BigInt

Web3.js

Web3.js uses a custom BigNumber implementation:

const amount = web3.utils.toWei('1.5', 'ether'); // "1500000000000000000"

const formatted = web3.utils.fromWei(amount, 'ether'); // "1.5"

// Math operations require BigInt conversion

const doubled = (BigInt(amount) * 2n).toString();

Web3.js returns strings for large numbers, requiring manual conversion for math.

Bundle Size

Ethers.js: ~116 KB minified

Web3.js: ~600 KB minified

For production apps, bundle size matters:

  • Ethers.js is 5x smaller, leading to faster load times
  • Critical for mobile users and performance-sensitive dApps

TypeScript Support

Ethers.js

Native TypeScript — written in TypeScript from the ground up:

import { BrowserProvider, Contract, Signer } from 'ethers';

const provider: BrowserProvider = new BrowserProvider(window.ethereum);

const signer: Signer = await provider.getSigner();

Full type inference, no @types package needed.

Web3.js

Added via @types/web3 — requires separate type definitions:

npm install --save-dev @types/web3
import { Web3 } from 'web3';

const web3: Web3 = new Web3(window.ethereum);

Types are less precise and can be outdated.

ENS (Ethereum Name Service) Support

Ethers.js

Built-in ENS support — resolve names to addresses automatically:

const address = await provider.resolveName('vitalik.eth');

// "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"

const name = await provider.lookupAddress(address);

// "vitalik.eth"

// Use ENS directly in transactions

const tx = await signer.sendTransaction({

to: 'vitalik.eth',

value: ethers.parseEther('0.1')

});

Web3.js

Requires separate package (web3-eth-ens):

npm install web3-eth-ens
const address = await web3.eth.ens.getAddress('vitalik.eth');

Less integrated, more setup required.

Migration Guide: Web3.js → Ethers.js

Provider Setup

Before (Web3.js):

const web3 = new Web3(window.ethereum);

After (Ethers.js):

const provider = new BrowserProvider(window.ethereum);

const signer = await provider.getSigner();

Getting Balance

Before:

const balance = await web3.eth.getBalance(address);

const eth = web3.utils.fromWei(balance, 'ether');

After:

const balance = await provider.getBalance(address);

const eth = ethers.formatEther(balance);

Contract Interaction

Before:

const contract = new web3.eth.Contract(abi, address);

const balance = await contract.methods.balanceOf(owner).call();

After:

const contract = new Contract(address, abi, provider);

const balance = await contract.balanceOf(owner);

Sending Transactions

Before:

await contract.methods.transfer(to, amount).send({ from: account });

After:

const tx = await contract.transfer(to, amount);

await tx.wait();

Which Should You Choose?

Choose Ethers.js if:

  • You're starting a new project
  • Bundle size matters (mobile, performance)
  • You use TypeScript
  • You want modern, clean API
  • ENS support is needed
  • You prefer active maintenance and updates

Choose Web3.js if:

  • You're maintaining legacy code
  • Your team is already familiar with Web3.js
  • You need compatibility with old tools
  • You prefer the established ecosystem
  • You're migrating to Web3.js v4 (much improved)

Conclusion

Ethers.js is the modern choice for most new projects. Its smaller bundle size, cleaner API, native TypeScript support, and built-in ENS make it ideal for production dApps. Web3.js remains viable for legacy projects, but Ethers.js has become the de facto standard for new development.

Start building with Ethers.js today — integrate it into your dApp and experience the difference. Practice blockchain interactions with Solingo's hands-on Web3 challenges to master both libraries.

Next steps:

  • Migrate an existing Web3.js project to Ethers.js
  • Build a dApp using Ethers.js from scratch
  • Compare bundle sizes in your production build
  • Explore advanced features like multicall and custom providers

Prêt à mettre en pratique ?

Applique ces concepts avec des exercices interactifs sur Solingo.

Commencer gratuitement