Boilerplate Overview: NodeJS + React
Boilerplate is a pre-written codebase that speeds up development by providing a foundational template for building extensions.
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
@gofynd/fdk-extension-javascript
for interacting with Fynd Commerce.@gofynd/fdk-client-javascript
a peer dependency for the previous library.express
for running our server.sqlite3
for session storage.- Middlewares like
dotenv
andserve-static
- Development dependencies like
nodemon
,jest
, andsupertest
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
- We serve the index.html with the root element and script
<div id="root"></div>
<script src="./index.jsx"></script>
- We render
react-router-dom
withrouter
in the root DOM in index.jsx.
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
)
- 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 />,
}
])
- 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>