Next.js Discord

Discord Forum

Next 14, Google Sheets API request via api route

Answered
American Crocodile posted this in #help-forum
Open in Discord
American CrocodileOP
Hi,

I have a simple next app with an api route at app/api/data/route.js

The majority of the code in the file comes from the latest node.js example in the google sheets api docs.

import { google } from 'googleapis';
import { authenticate } from '@google-cloud/local-auth';
import path from 'path';
import fs from 'fs/promises';

const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly'];

const TOKEN_PATH = path.join(process.cwd(), './token.json');
const CREDENTIALS_PATH = path.join(process.cwd(), './credentials.json');

async function loadSavedCredentialsIfExist() {
  try {
    const content = await fs.readFile(TOKEN_PATH);
    const credentials = JSON.parse(content);
    return google.auth.fromJSON(credentials);
  } catch (err) {
    return null;
  }
}

async function saveCredentials(client) {
  const content = await fs.readFile(CREDENTIALS_PATH);
  const keys = JSON.parse(content);
  const key = keys.installed;
  const payload = JSON.stringify({
    type: 'authorized_user',
    client_id: key.client_id,
    client_secret: key.client_secret,
    refresh_token: client.credentials.refresh_token,
  });
  await fs.writeFile(TOKEN_PATH, payload);
}

async function authorize() {
  let client = await loadSavedCredentialsIfExist();
  if (client) {
    return client;
  }
  client = await authenticate({
    scopes: SCOPES,
    keyfilePath: CREDENTIALS_PATH,
  });
  if (client.credentials) {
    await saveCredentials(client);
  }
  return client;
}

export async function GET() {

  try {
    const authClient = await authorize();
    const sheets = google.sheets({ version: 'v4', auth: authClient });

    const response = await sheets.spreadsheets.values.get({
      spreadsheetId: 'mysheetid',
      range: 'A1:S16',
    });

    const rows = response.data.values;
    return new Response(JSON.stringify(rows), {
      status: 200,
      headers: {
        'Content-Type': 'application/json',
      },
    });
  } catch (error) {
    console.error('Error accessing Sheets API', error);
    return new Response(JSON.stringify({ error: 'Error accessing Sheets API' }), {
      status: 500,
      headers: {
        'Content-Type': 'application/json',
      },
    });
  }
}


And in a regular route. Such as app/page.jsx I make a request to the API route above.

    useEffect(() => {
        fetch('/api/data')
          .then(response => response.json())
          .then(data => setSheetData(data))
          .catch(error => console.error('Error fetching sheet data:', error));
      }, []);


I get the following error.

Error accessing Sheets API Error: Cannot find module 'C:\Users\ASUS\projects\clients\illbruck-product-calculator\membrane-selector\credentials.json'
    at webpackEmptyContext (C:\Users\ASUS\myapp\.next\server\app\api\data\route.js:22:10)
    at authenticate (webpack-internal:///(rsc)/./node_modules/@google-cloud/local-auth/build/src/index.js:47:114)
    at authorize (webpack-internal:///(rsc)/./app/api/data/route.js:52:90)
    at async GET (webpack-internal:///(rsc)/./app/api/data/route.js:63:28)
    at async C:\Users\ASUS\myapp\node_modules\next\dist\compiled\next-server\app-route.runtime.dev.js:6:63251 {
  code: 'MODULE_NOT_FOUND'
}


Perhaps the error is confusing me. "Cannot find module", yet there we're not requesting a module.

The app has very little other than the above code. There are no other errors. Is my implementation wrong for next.js?
Answered by Ray
try add this in next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverComponentsExternalPackages: [
      "googleapis",
      "@google-cloud/local-auth",
    ],
  },
};

module.exports = nextConfig;
View full answer

7 Replies

American CrocodileOP
To note; I have also supplied the CREDENTIALS_PATH as the direct path. The path is the same, tested by printing at various levels/steps.
@American Crocodile Hi, I have a simple next app with an api route at app/api/data/route.js The majority of the code in the file comes from the latest node.js example in the google sheets api docs. js import { google } from 'googleapis'; import { authenticate } from '@google-cloud/local-auth'; import path from 'path'; import fs from 'fs/promises'; const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly']; const TOKEN_PATH = path.join(process.cwd(), './token.json'); const CREDENTIALS_PATH = path.join(process.cwd(), './credentials.json'); async function loadSavedCredentialsIfExist() { try { const content = await fs.readFile(TOKEN_PATH); const credentials = JSON.parse(content); return google.auth.fromJSON(credentials); } catch (err) { return null; } } async function saveCredentials(client) { const content = await fs.readFile(CREDENTIALS_PATH); const keys = JSON.parse(content); const key = keys.installed; const payload = JSON.stringify({ type: 'authorized_user', client_id: key.client_id, client_secret: key.client_secret, refresh_token: client.credentials.refresh_token, }); await fs.writeFile(TOKEN_PATH, payload); } async function authorize() { let client = await loadSavedCredentialsIfExist(); if (client) { return client; } client = await authenticate({ scopes: SCOPES, keyfilePath: CREDENTIALS_PATH, }); if (client.credentials) { await saveCredentials(client); } return client; } export async function GET() { try { const authClient = await authorize(); const sheets = google.sheets({ version: 'v4', auth: authClient }); const response = await sheets.spreadsheets.values.get({ spreadsheetId: 'mysheetid', range: 'A1:S16', }); const rows = response.data.values; return new Response(JSON.stringify(rows), { status: 200, headers: { 'Content-Type': 'application/json', }, }); } catch (error) { console.error('Error accessing Sheets API', error); return new Response(JSON.stringify({ error: 'Error accessing Sheets API' }), { status: 500, headers: { 'Content-Type': 'application/json', }, }); } } And in a regular route. Such as app/page.jsx I make a request to the API route above. js useEffect(() => { fetch('/api/data') .then(response => response.json()) .then(data => setSheetData(data)) .catch(error => console.error('Error fetching sheet data:', error)); }, []); I get the following error. sh Error accessing Sheets API Error: Cannot find module 'C:\Users\ASUS\projects\clients\illbruck-product-calculator\membrane-selector\credentials.json' at webpackEmptyContext (C:\Users\ASUS\myapp\.next\server\app\api\data\route.js:22:10) at authenticate (webpack-internal:///(rsc)/./node_modules/@google-cloud/local-auth/build/src/index.js:47:114) at authorize (webpack-internal:///(rsc)/./app/api/data/route.js:52:90) at async GET (webpack-internal:///(rsc)/./app/api/data/route.js:63:28) at async C:\Users\ASUS\myapp\node_modules\next\dist\compiled\next-server\app-route.runtime.dev.js:6:63251 { code: 'MODULE_NOT_FOUND' } Perhaps the error is confusing me. "Cannot find module", yet there we're not requesting a module. The app has very little other than the above code. There are no other errors. Is my implementation wrong for next.js?
try add this in next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverComponentsExternalPackages: [
      "googleapis",
      "@google-cloud/local-auth",
    ],
  },
};

module.exports = nextConfig;
Answer
American CrocodileOP
Yes sir. That was it. I would not have known to do this! So does this mean with servercomponents using external packages that they will need to be added to the config?
And sir, thank you. Had been here for some time.
@American Crocodile Yes sir. That was it. I would not have known to do this! So does this mean with servercomponents using external packages that they will need to be added to the config?
If a dependency is using Node.js specific features, you can choose to opt-out specific dependencies from the Server Components bundling and use native Node.js require.
https://nextjs.org/docs/app/api-reference/next-config-js/serverComponentsExternalPackages
next already includes some popular package, you can check it here
American CrocodileOP
Understood, okay. Thank you very much