Step 3: Integrate the BigCommerce API and Add a Database
Now that you have embedded your app in the BigCommerce platform, you’re ready to integrate the BigCommerce API.
Anytime you make an API call to BigCommerce, you need to pass in the access token. Storing the access token in a database allows you to persist the session when you call /auth, /load, or /uninstall endpoints.
This step demonstrates how to integrate the sample app with Cloud Firestore , a cloud-hosted NoSQLFirebase database, and MySQL , a relational database management system.
Install npm packages
If using Firebase, install firebase, jsonwebtoken, and swr npm packages.
npm install --save firebase jsonwebtoken swrIf using MySQL, install mysql2, jsonwebtoken, and swr npm packages.
npm install --save mysql2 jsonwebtoken swrFirebase version
These instructions have been tested using the firebase v9 package. You can view a list of all the tested package versions in the package.json file on the Step 3 branch of this sample app’s repo.
Add scripts
-
Open
package.jsonin your text editor. -
Update the
scriptsproperty to include thedb:setupscript.
{
...
"scripts": {
...,
"db:setup": "node scripts/db.js"
}
}- Save your changes.
Add TypeScript definitions
-
In the root directory of your project, add a
typesfolder. -
In the
typesfolder, createauth.ts,db.ts, andindex.tsfiles. -
Open the
auth.tsfile and exportUser,SessionProps, andQueryParamsTypeScript type definitions. You can view auth.ts (GitHub) .
export interface User {
id: number;
username?: string;
email: string;
}
export interface SessionProps {
access_token?: string;
scope?: string;
user: User;
context: string;
store_hash?: string;
timestamp?: number;
}
export interface QueryParams {
[key: string]: string | string[];
}- Open the
db.tsfile. ImportSessionPropsfrom./indexand exportStoreData,UserData, andDbTypeScript type definitions. You can view db.ts (GitHub) .
import { SessionProps } from './index';
export interface StoreData {
accessToken?: string;
scope?: string;
storeHash: string;
}
export interface UserData {
email: string;
username?: string;
}
export interface Db {
setUser(session: SessionProps): Promise<void>;
setStore(session: SessionProps): Promise<void>;
getStoreToken(storeHash: string): string | null;
deleteStore(session: SessionProps): Promise<void>;
}- Open the
index.tsfile and export all interfaces. You can view index.ts (GitHub) .
export * from './auth';
export * from './db';Initialize React Context
React’s Context API is a state management tool that streamlines the process of passing data to multiple components at different nesting levels. It lets you pass data through the component tree without having to pass props through multiple levels of React components. To learn more about Context, see React’s Context guide .
-
In the root of your app, create a
contextfolder. -
In the
contextfolder, create asession.tsxfile. -
Add the logic to create a context. You can view session.tsx (GitHub) .
import { useRouter } from 'next/router';
import { createContext, useContext, useEffect, useState } from 'react';
const SessionContext = createContext({ context: '' });
const SessionProvider = ({ children }) => {
const { query } = useRouter();
const [context, setContext] = useState('');
useEffect(() => {
if (query.context) {
setContext(query.context.toString());
}
}, [query.context]);
return (
<SessionContext.Provider value={{ context }}>
{children}
</SessionContext.Provider>
);
};
export const useSession = () => useContext(SessionContext);
export default SessionProvider;Update environment variables
You use a JSON Web Token (JWT) to securely transmit information encoded as a JSON object between parties. To learn more about JWT, see the Internet Engineering Task Force documentation .
-
Open the
.envfile. -
Enter a JWT secret. Your JWT key should be at least 32 random characters (256 bits) for HS256.
JWT_KEY={SECRET}Update the auth lib page
-
In the
libfolder, open theauth.tsfile. -
At the top of the file, add the following imports:
import * as jwt from 'jsonwebtoken';
import * as BigCommerce from 'node-bigcommerce';
import { NextApiRequest, NextApiResponse } from 'next';
import { QueryParams, SessionProps } from '../types';
import db from './db';- Below the import statements, add the following line of code to destructure environment variables from
.env:
const { AUTH_CALLBACK, CLIENT_ID, CLIENT_SECRET, JWT_KEY } = process.env;- Remove the
process.envglobal variable from the BigCommerce instances.
const bigcommerce = new BigCommerce({
logLevel: 'info',
clientId: CLIENT_ID,
secret: CLIENT_SECRET,
callback: AUTH_CALLBACK,
responseType: 'json',
headers: { 'Accept-Encoding': '*' },
apiVersion: 'v3'
});
const bigcommerceSigned = new BigCommerce({
secret: CLIENT_SECRET,
responseType: 'json'
});- Remove the
QueryParamsinterface.
//Delete this code
interface QueryParams {
[key: string]: string;
}- Below the
bigcommerceSignedvariable, export thebigcommerceClientfunction.
export function bigcommerceClient(accessToken: string, storeHash: string) {
return new BigCommerce({
clientId: CLIENT_ID,
accessToken,
storeHash,
responseType: 'json',
apiVersion: 'v3'
});
}- Export
getBCAuthandgetBCVerifyfunctions.
export function getBCAuth(query: QueryParams) {
return bigcommerce.authorize(query);
}
export function getBCVerify({ signed_payload_jwt }: QueryParams) {
return bigcommerceSigned.verifyJWT(signed_payload_jwt);
}- Add the
setSession,getSession, andremoveDataStorefunctions.
export async function setSession(session: SessionProps) {
db.setUser(session);
db.setStore(session);
}
export async function getSession({ query: { context = '' } }: NextApiRequest) {
if (typeof context !== 'string') return;
const decodedContext = decodePayload(context)?.context;
const accessToken = await db.getStoreToken(decodedContext);
return { accessToken, storeHash: decodedContext };
}
export async function removeDataStore(res: NextApiResponse, session: SessionProps) {
await db.deleteStore(session);
}- Add the
encodePayloadanddecodePayloadfunctions. You can view auth.ts (GitHub)
export function encodePayload({ ...session }: SessionProps) {
const contextString = session?.context ?? session?.sub;
const context = contextString.split('/')[1] || '';
return jwt.sign({ context }, JWT_KEY, { expiresIn: '24h' });
}
export function decodePayload(encodedContext: string) {
return jwt.verify(encodedContext, JWT_KEY);
}Add a database
In this section of the tutorial, we provide config and initialization code for both Firebase and MySQL databases. Depending on the database you choose to integrate your app with, use the configuration instructions specific to your setup.
For Firebase configuration instructions, see Set up Firebase database.
For MySQL configuration instructions, see Set up MySQL database.
Set up Firebase database
Cloud Firestore is a cloud-hosted NoSQL Firebase database built on Google’s infrastructure. To learn more about Firebase, including how-to guides and code samples, see Firebase Documentation . For a quickstart on how to set up your Cloud Firestore, see Get started .
Create a Firebase project
-
Sign in to Cloud Firestore using your Google account. To create a Google account, visit the Google signup page .
-
Once logged in, click Go to console in the top right corner.
-
In the Firebase console, click Add project.
-
Enter your project name and click Continue.
-
Click Create project.
Create a Firebase config
-
In your Firebase project console, click on the settings icon that resembles a gear in the top left corner.
-
Select Project settings from the dropdown menu.
-
Under the General tab, scroll down to Your apps and click on the code icon (
</>) to select the web platform. -
Type in the name of your app and click Register app.
-
Make a note of the Firebase
apiKey,authDomain, andprojectId. You will need that information to update the app’s environment variables.
Create a Cloud Firestore database
-
In your Firebase console, click Firestore Database under Build in the left pane. Follow the steps to create a Cloud Firestore database.
-
Click Create database.
-
Choose Start in test mode.
-
Select your Cloud Firestore location and click Enable.
Update environment variables
- In the
.envfile, specify the database type.
DB_TYPE=firebase- Enter your Firebase database config keys.
FIRE_API_KEY={firebaseConfig.apiKey}
FIRE_DOMAIN={firebaseConfig.authDomain}
FIRE_PROJECT_ID={firebaseConfig.projectId}Configure the Firebase database
-
In the
libfolder, create adbsfolder. -
In the
dbsfolder, create afirebase.tsfile. -
At the top of the file, import the Firebase packages and TypeScript definitions. You can view firebase.ts (GitHub) .
import { initializeApp } from 'firebase/app';
import { deleteDoc, doc, getDoc, getFirestore, setDoc, updateDoc } from 'firebase/firestore';
import { SessionProps, UserData } from '../../types';- Add the Firebase config and initialization logic. You can view firebase.ts (GitHub)
// Firebase config and initialization
// Prod applications might use config file
const { FIRE_API_KEY, FIRE_DOMAIN, FIRE_PROJECT_ID } = process.env;
const firebaseConfig = {
apiKey: FIRE_API_KEY,
authDomain: FIRE_DOMAIN,
projectId: FIRE_PROJECT_ID,
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
// Firestore data management functions
export async function setUser({ user }: SessionProps) {
if (!user) return null;
const { email, id, username } = user;
const ref = doc(db, 'users', String(id));
const data: UserData = { email };
if (username) {
data.username = username;
}
await setDoc(ref, data, { merge: true });
}
export async function setStore(session: SessionProps) {
const {
access_token: accessToken,
context,
scope,
user: { id },
} = session;
// Only set on app install or update
if (!accessToken || !scope) return null;
const storeHash = context?.split('/')[1] || '';
const ref = doc(db, 'store', storeHash);
const data = { accessToken, adminId: id, scope };
await setDoc(ref, data);
}
export async function getStoreToken(storeHash: string) {
if (!storeHash) return null;
const storeDoc = await getDoc(doc(db, 'store', storeHash));
return storeDoc.data()?.accessToken ?? null;
}
export async function deleteStore({ store_hash: storeHash }: SessionProps) {
const ref = doc(db, 'store', storeHash);
await deleteDoc(ref);
}- Running
firebase.initializeApp()will initialize the app. For initialized apps, callfirebase.app()to retrieve the Firebase app instance.
Set up MySQL database
MySQL is a relational database management system. For instructions on how to set up and use MySQL, see Getting Started with MySQL . Once you complete the database setup, make a note of the MySQL host, domain, username, password, and port variables. You will need them to update the app’s environment variables in the next step.
Update environment variables
- In the
.envfile, specify the database type.
DB_TYPE=mysql- Enter your MySQL database config keys.
MYSQL_HOST={mysql host}
MYSQL_DATABASE={mysql domain}
MYSQL_USERNAME={mysql username}
MYSQL_PASSWORD={mysql password}
MYSQL_PORT={mysql port}Configure MySQL
-
In the
dbsfolder, create amysql.tsfile. -
At the top of the file, add the following imports:
import * as mysql from 'mysql2';
import { promisify } from 'util';
import { SessionProps, StoreData } from '../../types';- Add the MySQL config and initialization logic. You can view mysql.ts (GitHub) .
const MYSQL_CONFIG = {
host: process.env.MYSQL_HOST,
database: process.env.MYSQL_DATABASE,
user: process.env.MYSQL_USERNAME,
password: process.env.MYSQL_PASSWORD,
...(process.env.MYSQL_PORT && { port: process.env.MYSQL_PORT }),
};
// For use with Heroku ClearDB
// Other mysql: https://www.npmjs.com/package/mysql#pooling-connections
const pool = mysql.createPool(process.env.CLEARDB_DATABASE_URL ? process.env.CLEARDB_DATABASE_URL : MYSQL_CONFIG);
const query = promisify(pool.query.bind(pool));
export async function setUser({ user }: SessionProps) {
if (!user) return null;
const { email, id, username } = user;
const userData = { email, userId: id, username };
await query('REPLACE INTO users SET ?', userData);
}
export async function setStore(session: SessionProps) {
const { access_token: accessToken, context, scope } = session;
// Only set on app install or update
if (!accessToken || !scope) return null;
const storeHash = context?.split('/')[1] || '';
const storeData: StoreData = { accessToken, scope, storeHash };
await query('REPLACE INTO stores SET ?', storeData);
}
export async function getStoreToken(storeHash: string) {
if (!storeHash) return null;
const results = await query('SELECT accessToken from stores limit 1');
return results.length ? results[0].accessToken : null;
}
export async function deleteStore({ store_hash: storeHash }: SessionProps) {
await query('DELETE FROM stores WHERE storeHash = ?', storeHash);
}Set up a db lib page
-
In the
libfolder, create adb.tsfile. -
Open the
db.tsfile and add the following imports at the top of the file.
import * as firebaseDB from './dbs/firebase';
import * as sqlDB from './dbs/mysql';
import { Db } from '../types';- Add the switch expression to determine which database code to execute. You can view db.ts (GitHub) .
const { DB_TYPE } = process.env;
let db: Db;
switch (DB_TYPE) {
case 'firebase':
db = firebaseDB;
break;
case 'mysql':
db = sqlDB;
break;
default:
db = firebaseDB;
break;
}
export default db;Run the initial database migration
If you’re using MySQL, set up the initial tables by navigating to the root directory of your project and running the following script:
npm run db:setupUpgrade the endpoints
Auth endpoint
-
Open the
auth.tsfile nested inside thepages/apifolder.- Import
encodePayload,getBCVerify, andsetSessionfrom/lib/auth. Your imports should now look like this:
- Import
import { NextApiRequest, NextApiResponse } from 'next';
import { encodePayload, getBCAuth, setSession } from '../../lib/auth';- Update the logic to authenticate the app on install. You can view auth.ts (GitHub) .
export default async function auth(req: NextApiRequest, res: NextApiResponse) {
try {
// Authenticate the app on install
const session = await getBCAuth(req.query);
const encodedContext = encodePayload(session); // Signed JWT to validate/ prevent tampering
await setSession(session);
res.redirect(302, `/?context=${encodedContext}`);
} catch (error) {
const { message, response } = error;
res.status(response?.status || 500).json({ message });
}
}Load endpoint
-
Open the
load.tsfile nested inside thepages/apifolder. -
Import
encodePayload,getBCVerify, andsetSessionfrom/lib/auth. Your imports should now look like this:
import { NextApiRequest, NextApiResponse } from 'next';
import { encodePayload, getBCVerify, setSession } from '../../lib/auth';- Update the logic to authenticate the app on load. You can view load.ts (GitHub) .
export default async function load(req: NextApiRequest, res: NextApiResponse) {
try {
// Verify when app loaded (launch)
const session = await getBCVerify(req.query);
const encodedContext = encodePayload(session); // Signed JWT to validate/ prevent tampering
await setSession(session);
res.redirect(302, `/?context=${encodedContext}`);
} catch (error) {
const { message, response } = error;
res.status(response?.status || 500).json({ message });
}
}Uninstall endpoint
-
Open the
uninstall.tsfile nested inside thepages/apifolder. -
Import
getBCVerifyandremoveSessionfrom/lib/auth. Your imports should now look like this:
import { NextApiRequest, NextApiResponse } from 'next';
import { getBCVerify, removeSession } from '../../lib/auth';- Update the logic to delete the session at time of uninstall. You can view uninstall.ts (GitHub) .
export default async function uninstall(req: NextApiRequest, res: NextApiResponse) {
try {
const session = await getBCVerify(req.query);
await removeSession(res, session);
res.status(200).end();
} catch (error) {
const { message, response } = error;
res.status(response?.status || 500).json(message);
}
}Add the Products endpoint
The Products endpoint retrieves your products summary from the Catalog API.
-
In the
pages/apifolder, create a new folder calledproducts. -
In the
productsfolder, create anindex.tsfile. This will create a/productsroute. -
At the top of the file, import the following packages:
import { NextApiRequest, NextApiResponse } from 'next';
import { bigcommerceClient, getSession } from '../../../lib/auth';- Add the async
productsfunction, which awaits the data returned frombigcommerce.get. You can view index.ts (GitHub) . Theproductsfunction calls thegetSessionfunction to retrieve the session’s access token and store hash.
export default async function products(req: NextApiRequest, res: NextApiResponse) {
try {
// First, retrieve the session by calling:
const { accessToken, storeHash } = await getSession(req);
// Then, connect the Node API client (to make API calls to BigCommerce)
const bigcommerce = bigcommerceClient(accessToken, storeHash);
// For this example, we'll be connecting to the Catalog API
const { data } = await bigcommerce.get('/catalog/summary');
res.status(200).json(data);
// Finally, handle errors
} catch (error) {
const { message, response } = error;
res.status(response?.status || 500).json({ message });
}
}Create a custom hook
To consume the Products endpoint, create a custom React hook using SWR .
-
In the
libfolder, create ahooks.tsfile. -
At the top of the file, import the
useSWRReact hook from SWR anduseSessionfrom Context.
import useSWR from 'swr';
import { useSession } from '../context/session';- Declare the
fetcherfunction.
function fetcher(url: string, encodedContext: string) {
return fetch(`${url}?context=${encodedContext}`).then(res => res.json());
}The fetcher function accepts the API URL and returns data asynchronously.
- Export the
useProductsfunction. You can view hooks.ts (GitHub) .
// Reusable SWR hooks
// https://swr.vercel.app/
export function useProducts() {
const encodedContext = useSession()?.context;
// Request is deduped and cached; Can be shared across components
const { data, error } = useSWR(encodedContext ? ['/api/products', encodedContext] : null, fetcher);
return {
summary: data,
isError: error,
};
}useSWR accepts two arguments: the API URL and the fetcher function. The fetcher function takes the /api/products URL passed in from the useProduct function. The useProducts function destructures the data returned by the useSWR hook.
Create a header component
-
In the app’s root directory, create a
componentsfolder. -
In the
components folder, create aheader.tsxfile. -
Import
BoxandLinkcomponents from BigDesign.
import { Box, Link } from '@bigcommerce/big-design';- Define the
Headerfunctional component. You can view header.tsx (GitHub) .
const Header = () => (
<Box marginBottom="xxLarge">
<Link href="#">Home</Link>
</Box>
);
export default Header;Update the homepage
-
In the
pagesfolder, open theindex.tsxfile. -
At the top of the file, replace the existing import with the following:
import { Box, Flex, Panel, Text } from '@bigcommerce/big-design';
import { useProducts } from '../lib/hooks';- Update the
Indexfunctional component. You can view index.tsx (GitHub) .
const Index = () => {
const { summary } = useProducts();
return (
<Panel header="Homepage">
{summary &&
<Flex>
<Box marginRight="xLarge">
<Text>Inventory Count</Text>
<Text>{summary.inventory_count}</Text>
</Box>
<Box marginRight="xLarge">
<Text>Variant Count</Text>
<Text>{summary.variant_count}</Text>
</Box>
<Box>
<Text>Primary Category</Text>
<Text>{summary.primary_category_name}</Text>
</Box>
</Flex>
}
</Panel>
);
};
export default Index;summary creates the Flex component with three Box components inside of it. inventory_count, variant_count, and primary_category_name are populated with data returned from calling the /catalog/summary endpoint added in Add the Products endpoint.
For the complete list of properties returned by the /catalog/summary endpoint, see Get a Catalog Summary.
Update the user interface
-
In the root of the pages folder, open the
_app.tsxfile. -
Import the
BoxandHeadercomponents.
import { Box, GlobalStyles } from '@bigcommerce/big-design';
import Header from '../components/header';- Import
SessionProviderfrom Context.
import SessionProvider from '../context/session';Your updated import statements should resemble the following:
import { Box, GlobalStyles } from '@bigcommerce/big-design';
import type { AppProps } from 'next/app';
import Header from '../components/header';
import SessionProvider from '../context/session';- For Context to properly propagate, we need to wrap
<Component {...pageProps} />with the ContextSessionProvider. This ensures that each page has access to the React Context.
<SessionProvider>
<Component {...pageProps} />
</SessionProvider>- Add a
Boxcomponent and place theHeaderandSessionProvidercomponents inside it. You can view _app.tsx (GitHub) .
const MyApp = ({ Component, pageProps }: AppProps) => (
<>
<GlobalStyles />
<Box marginHorizontal="xxxLarge" marginVertical="xxLarge">
<Header />
<SessionProvider>
<Component {...pageProps} />
</SessionProvider>
</Box>
</>
);
export default MyApp;-
In the root of the
pagesfolder, openindex.tsx. -
Import the
Headercomponent. You can view index.tsx (GitHub) .
import Header from '../components/header';Test your app
Now that you have synced up the database, your app should display information under Inventory Count, Variant Count, and Primary Category fields.

Next: Enhance the User Experience with BigDesign