Skip to main content

Boilerplate Overview: NodeJS + React

Boilerplate is a pre-written codebase that speeds up development by providing a foundational template for building extensions.

Prerequisite

If you do not have the boilerplate, please go through the steps in the Get Started page.

Alternatively, if you have already setup FDK CLI, run the fdk ext init --template node-react command to dowload the boilerplate explained in this page.

Directory Structure

├── README.md
├── .env # Environment variables
├── extension.context.json # DO NOT TOUCH. Managed FDK CLI
├── index.js # Entry file for the backend
├── server.js # Main server file for the backend
├── frontend # Frontend
│ ├── README.md
│ ├── index.html # Entry point for the frontend
│ ├── index.jsx
│ ├── router.jsx # React Router configuration
│ ├── App.jsx
│ ├── pages
│ │ ├── Home.jsx # Home page component
│ │ ├── NotFound.jsx
│ │ └── style/ # Page styles
│ ├── test/ # Frontend tests
│ └── package.json
├── test/ # Backend tests
└── package.json

Backend

We use the JavaScript Extension Helper Library to handle authentication, use SDKs methods for APIs, and subscribe to webhooks.

Dependencies

Auth Credentials

The FDK CLI configures the API key and secret during the development process when running the command fdk ext preview. The credentials are taken from the Partners panel, saved in the extension.context.json file, and sent to the backend (index.js) as environment variables.

Find API credentials: Partner panel → Extensions → <your-extension> → Credentials

Environment Variables

You can add custom environment variables in .env file and they can be accessed in the index.js under the process.env object. As a good practice, use capital letters for naming the environment variables.

The following variables are overridden by FDK CLI during development. But, you must set them with the approriate values before deploying to production.

Production Values: EXTENSION_API_KEY, EXTENSION_API_SECRET, EXTENSION_BASE_URL

The following variables are overridden by FDK CLI during development, avoid using them:

Avoid: FRONTEND_PORT, BACKEND_PORT, FP_API_DOMAIN

Setup FDK

In index.js, we call the setupFdk function from @gofynd/fdk-extension-javascript/express with the authentication details, post-auth actions, storage setup, access mode, and webhook configuration. Whenever the seller open the extension, Fynd Commerce redirects them to the URL returned from the callbacks.auth function. During development using FDK CLI these process.env variables will set by our CLI.

const fdkExtension = setupFdk({
api_key: process.env.EXTENSION_API_KEY,
api_secret: process.env.EXTENSION_API_SECRET,
base_url: process.env.EXTENSION_BASE_URL,
callbacks: {
auth: async (req) => `${req.extension.base_url}/company/${req.query.company_id}`,
uninstall: async (req) => { },
},
storage: new SQLiteStorage(sqliteInstance, 'example-fynd-extension'),
access_mode: 'online',
webhook_config: {
api_path: '/api/webhook-events',
notification_email: 'useremail@example.com',
event_map: {
'company/product/create': {
'handler': (eventName) => { console.log(eventName) },
'version': '2',
},
},
},
})

API Mounting

We use the extension helper library to manage authentication and add extra paths (like /fp/auth and /fp/uninstall) to our express server. This is done by mounting fdkExtension.fdkHandler on our base path.

app.use('/', fdkExtension.fdkHandler)

We use the platformClient to call make API calls to the Fynd Commerce – Platform API. We obtain this object by mounting fdkExtension.platformApiRoutes on our express router. Every route under the path we mount on would have the platformClient in the request object.

app.use('/api', fdkExtension.platformApiRoutes)

API Routing

We create our api paths using the fdkExtension.platformApiRoutes router object. This ensures that we have access to the platformClient.

fdkExtension.platformApiRoutes.get('/products' async function view(req, res, next) {
try {
const data = await req.platformClient.catalog.getProducts()
return res.json(data)
} catch (err) {
next(err)
}
})

Webhook Handler

After initialising an express server using const app = express(), we setup the webhook callback path we have given in webhook_config.api_path during setupFdk. We call the webhookRegistry.processWebhook to verify that the webhook call is coming form Fynd Commerce.

app.post('/api/webhook-events', async function (req, res) {
try {
await fdkExtension.webhookRegistry.processWebhook(req)
return res.status(200).json({ 'success': true })
} catch (err) {
return res.status(500).json({ 'success': false })
}
})

Frontend routes

For all paths other than those previously defined, we serve the index.html file from the frontend.

app.get('*', (req, res) => {
return res
.status(200)
.set('Content-Type', 'text/html')
.send(readFileSync(path.join(STATIC_PATH, 'index.html')))
})

Frontend

We use React for rendering the UI and use Vite for hot-module-reload and package management.

Dependencies

  • vite for package management.
  • react for rendering the UI.
  • axios for making API calls.
  • Peer & developement dependencies like react-dom, react-router-dom, url-join, axios-mock-adapter, @testing-library

Foundataion & Routing

  1. We serve the index.html with the root element and script
  <div id="root"></div>
<script src="./index.jsx"></script>
  1. We render react-router-dom with router in the root DOM in index.jsx.
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
)
  1. We render the <App /> component in the router.jsx based on the path /company/:id.
const router = createBrowserRouter([
{
path: '/company/:company_id/',
element: <App />,
}, {
path: '/*',
element: <NotFound />,
}
])
  1. We render the <Home /> component in the App.jsx.
function App() {
return (
<div className='root'>
<Home />
</div>
)
}

Home Page

In our Home.jsx component, we get the company_id from the parameter.

import { useParams } from 'react-router-dom'
...
const { company_id } = useParams()

Then we fetch the products in the given company using the API '/api/products' we've defined in the backend. We keep the

const fetchProducts = async () => {
setPageLoading(true)
try {
const { data } = await axios.get(urlJoin(EXAMPLE_MAIN_URL, '/api/products'),{
headers: { 'x-company-id': company_id }
})
setProductList(data.items)
} catch (e) {
console.error('Error fetching products:', e)
} finally {
setPageLoading(false)
}
}

Then we render the product details that we've fetched.

<div>
{productList.map((product, index) => (
...
<img src={productProfileImage(product.media)} alt={product.name} />
<div className="product-name" data-testid={`product-name-${product.id}`}>
{product.name}
</div>
...
)}
</div>
Code References

Was this section helpful?