In the previous module, we covered registering our Snyk App, setting up the authorization flow, and handling user authorization within our App. All of those topics are integral to the functionality of every Snyk App, but they're all what you might call "behind the scenes" topics.
In this module, we'll switch gears to focus on displaying content to the users who have authorized with our Snyk App. Specifically, we want to show unauthorized users a big button they can click to authorize and authorized users a list of their projects from Snyk.
Add a template engine to the Snyk App
While Express is perfectly capable of printing content to the screen and even rendering HTML server-side, life is much easier when using a template engine. For this tutorial we are using EJS.
First, install the node packages needed in this part of the tutorial:
npminstall--saveejs
Next, modify the initGlobalMiddlewares() function we created in our last module to tell express that we want to use a view engine, EJS in this case, and let it know where we'll be storing our view templates. We'll be storing our EJS templates in ./src/views and keeping any common files such as images and CSS in /.src/public.
For each route that we'll provide a template for, we'll need to modify the corresponding controller and ensure that we're using res.render("<template name>") rather than something more simplistic like res.send().
Example:
...private initRoutes() {this.router.get(`${this.path}`,this.indexPage);}private indexPage(req: Request, res: Response, next: NextFunction) {// THIS right here is what causes Express to render the EJS template.returnres.render("index");}...
That's all there is to it.
EJS templates support the concept of partial inclusion. While not strictly necessary, it makes sense to add a subdirectory to our ./src/views to differentiate partial templates like headers and footers from route templates. For the tutorial, we'll use ./src/views/partials to store such templates.
Basic EJS templates
The first template we'll create is a partial one, which we'll include in the other templates. This header.ejs will be the place we link stylesheets and other information that belong in the <head> of an HTML document.
These templates should be enough to get you started adding your own templates to any new routes you create. If you intend to continue using EJS, refer to the documentation for information about the features offered.
Rendering content for your Snyk App can be as simple or complex as you'd like it to be. Because we're dealing with JavaScript, the options are very flexible!
Showing users a list of projects
Now that we have some basic templates, take a look at how we can add some functionality to our Snyk App using a User's Snyk data. For this tutorial, we set up our app to allow users to view all of their projects within Snyk from within our app.
This is a basic and easily extendable feature.
We need to create:
A new route controller
A function (or functions) to pull the project data
An EJS template for showing the projects
We start with the API work, using the callSnykApi() function we created in the previous module. Since this directly relates to a particular route, we'll store this file with its controller. Following the pattern we've used throughout these tutorial modules, we'll create both files at ./src/routes/projects/.
// ./src/routes/projects/projectsHandler.tsimport { readFromDb } from"../../util/DB";import { callSnykApi } from"../../util/apiHelpers";import { EncryptDecrypt } from"../../util/encrypt-decrypt";import { AuthData } from"../../interfaces/DB";import { APIVersion } from"../../interfaces/API";import { ENCRYPTION_SECRET } from"../../app";/** * Get projects handler that fetches all user projects * from the Snyk API using user access token. This for * example purposes. In production it will depend on your * token scopes on what you can and can not access * @returns List of user project or an empty array */exportasyncfunctiongetProjectsFromApi():Promise<unknown[]> {// Read data from DBconstdb=awaitreadFromDb();constdata=mostRecent(db.installs);// If no data return empty arrayif (!data) return [];// Decrypt data(access token)consteD=newEncryptDecrypt(ENCRYPTION_SECRETasstring);constaccess_token=eD.decryptString(data?.access_token);consttoken_type=data?.token_type;// Call the axios instance configured for Snyk API v1constresult=awaitcallSnykApi( token_type, access_token,APIVersion.V1 ).post(`/org/${data?.orgId}/projects`);returnresult.data.projects || [];}/** * * @param{AuthData[]} installs get most recent install from list of installs * @returns the latest install or void */exportfunctionmostRecent(installs:AuthData[]):AuthData|void {if (installs) {return installs[installs.length-1]; }return;}
Next we'll write the route controller. Follow the pattern: ./src/routes/projects/projectsController.ts.
// ./src/routes/projects/projectsController.tsimporttype { Controller } from"../../interfaces/Controller";importtype { NextFunction, Request, Response } from"express";import { Router } from"express";import { getProjectsFromApi } from"./projectsHandler";exportclassProjectsControllerimplementsController {// The base URL path for this controllerpublic path ="/projects";// Express router for this controllerpublic router =Router();constructor() {this.initRoutes(); }privateinitRoutes() {// The route to render all user projects liststhis.router.get(`${this.path}`,this.getProjects); }privateasyncgetProjects(req:Request, res:Response, next:NextFunction) {try {constprojects=awaitgetProjectsFromApi();returnres.render("projects", { projects, }); } catch (error) {returnnext(error); } }}
Whenever we add a new route controller, we need to update ./index.ts to include it.
Using the project's API handler and controller we created in this module, you should have all you need to create your own custom code and make your Snyk App do whatever you'd like it to do.
We used the v1 API but keep an eye on Snyk's REST API. As additional features are added, you may find new or more efficient endpoints to use in your Snyk App.