I’m integrating Plaid into my Express.js web app to connect user accounts and synchronize transactions. My client-side code initializes Plaid Link, generating a Link token and user ID. After a successful connection, I exchange the public token for an access token to fetch transactions. On the server side, I’m using Express.js to handle Plaid interactions, with routes for Link token creation, public token exchange, and transaction retrieval. Access tokens are stored in an SQLite database based on user IDs. However, during the Plaid Link connection, I encounter the following problems:
“No access token found for user undefined” error on the server side.
The /transactions route receives an undefined user_id query parameter.
I’m seeking assistance in identifying the root cause of the “No access token found for user undefined” error and finding potential solutions. Any advice, insights, or suggestions would be greatly appreciated.
/public/views/transactions.ejs
<body>
<!-- Connect Accounts Button -->
<button id="connectButton">Connect with Plaid</button>
<div class="table-responsive">
<!-- Transaction Table -->
<table class="table border mb-0">
<!-- Table headers -->
<thead class="table-light fw-semibold">
<!-- Table header rows -->
</thead>
<tbody id="transactionList">
<!-- Transactions will be populated here dynamically -->
</tbody>
</table>
</div>
</div>
<!-- Plaid Link and JavaScript -->
<script src="https://cdn.plaid.com/link/v2/stable/link-initialize.js"></script>
<script>
async function createLinkToken() {
try {
const response = await fetch('/api/create_link_token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Failed to create Link token');
}
const data = await response.json();
console.log('Link token and user ID retrieved:', data);
return data;
} catch (error) {
console.error('Error creating Link token:', error);
throw error;
}
}
async function exchangePublicToken(publicToken) {
try {
const response = await fetch('/api/exchange_public_token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ public_token: publicToken }),
});
if (!response.ok) {
throw new Error('Failed to exchange public token');
}
const data = await response.json();
return data.access_token;
} catch (error) {
console.error('Error exchanging public token:', error);
throw error;
}
}
async function fetchTransactions(userId) {
try {
const response = await fetch(`/transactions?user_id=${encodeURIComponent(userId)}`);
if (!response.ok) {
throw new Error('Failed to fetch transactions');
}
const transactions = await response.json();
// Handle transactions data
} catch (error) {
console.error('Error fetching transactions:', error);
}
}
async function initializePlaid() {
const { link_token, user_id } = await createLinkToken();
const linkHandler = Plaid.create({
token: link_token,
onSuccess: async (publicToken, metadata) => {
console.log('Public token:', publicToken);
console.log('Metadata:', metadata);
try {
const accessToken = await exchangePublicToken(publicToken);
console.log('Access token:', accessToken);
fetchTransactions(user_id);
} catch (error) {
console.error('Error exchanging public token:', error);
}
},
});
linkHandler.open();
}
initializePlaid();
</script>
</body>
/public/app.js (Server-Side Code)
const { Configuration, PlaidApi, PlaidEnvironments } = require("plaid");
require('dotenv').config()
...
// Set up Plaid
const plaidConfig = new Configuration({
basePath: PlaidEnvironments[process.env.PLAID_ENV],
baseOptions: {
headers: {
"PLAID-CLIENT-ID": process.env.PLAID_CLIENT_ID,
"PLAID-SECRET": process.env.PLAID_SECRET,
"Plaid-Version": "2020-09-14",
},
},
});
const plaidClient = new PlaidApi(plaidConfig);
...
// Helper function to store tokens in the database
async function storeTokensInDatabase(userId, accessToken, itemId) {
return new Promise((resolve, reject) => {
db.run(
'INSERT INTO tokens(user_id, access_token, item_id) VALUES(?, ?, ?)',
[userId, accessToken, itemId],
function (err) {
if (err) {
console.error('Database error:', err.message);
reject(err);
} else {
console.log(`Tokens stored successfully with rowid ${this.lastID}`);
resolve();
}
}
);
});
}
async function retrieveAccessTokenFromDatabase(userId) {
console.log('Retrieving access token for user:', userId);
const dbConnection = new sqlite3.Database('./net_worth.db', (err) => {
if (err) {
console.error('Database error:', err.message);
reject(err);
} else {
console.log('Connected to the database');
}
});
return new Promise((resolve, reject) => {
dbConnection.get('SELECT access_token FROM tokens WHERE user_id = ?', [userId], (err, row) => {
if (err) {
console.error('Database error:', err.message);
reject(err);
} else if (!row) {
console.error(`No access token found for user ${userId}`);
reject(new Error(`No access token found for user ${userId}`));
} else {
dbConnection.close();
const accessToken = row.access_token;
console.log('Access token retrieved for user:', userId);
resolve(accessToken);
}
});
});
}
// Route to create a Link token
app.post('/api/create_link_token', async (req, res) => {
console.log('Received POST request to /api/create_link_token');
// Generate a unique user ID using the current date and time
const userId = Date.now().toString();
console.log('Generated userId:', userId);
try {
console.log('Creating Link token...');
const response = await plaidClient.linkTokenCreate({
user: {
client_user_id: userId,
},
client_name: 'Net Worth Tracker',
products: ['transactions'],
});
console.log('Link token created:', response.link_token);
// Store the user ID and Link token in the database
await storeLinkTokenInDatabase(userId, response.link_token);
// Retrieve the access token for the user
const accessToken = await retrieveAccessTokenFromDatabase(userId);
console.log('Access token retrieved for user:', userId);
// Send the Link token to the client
res.json({ link_token: response.link_token, user_id: userId });
} catch (error) {
console.error('Error creating Link token:', error);
res.status(500).send('Error creating Link token');
}
});
// Route to exchange a public token for an access token
app.post('/api/exchange_public_token', async (req, res) => {
console.log('Received userId when attempting to exchange a public token:', req.body.user_id);
console.log('Received userId when attempting to exchange a public token:', userId);
const publicToken = req.body.public_token;
const userId = req.body.user_id; // Get the user ID from the request
console.log('Received userId when attempting to create a link token:', userId);
try {
if (!publicToken || !userId) {
return res.status(400).json({ error: 'Invalid request parameters' });
}
const response = await plaidClient.itemPublicTokenExchange({
public_token: publicToken,
});
const accessToken = response.data.access_token;
const itemId = response.data.item_id;
// Store tokens in the database
await storeTokensInDatabase(userId, accessToken, itemId);
res.json({ message: 'Tokens exchanged successfully' });
} catch (err) {
console.error('Error exchanging public token:', err);
res.status(500).json({ error: 'Error exchanging public token' });
}
});
app.get('/transactions', async (req, res) => {
console.log('Received query parameters:', req.query);
const userId = req.query.user_id;
console.log('Received userId:', userId);
try {
const accessToken = await retrieveAccessTokenFromDatabase(userId);
console.log('Retrieved accessToken:', accessToken); // Log the accessToken
if (!accessToken) {
return res.status(400).json({ error: 'Access token not found for user' });
}
const response = await plaidClient.transactionsSync({ access_token: accessToken });
console.log('Plaid API Response:', response);
const transactions = response.data.transactions;
res.json(transactions);
} catch (err) {
console.error('Error fetching transactions:', err);
res.status(500).json({ error: 'Error fetching transactions' });
}
});