Introduction
In this post I will demonstrate how to create Monstarillo templates that generates code you like. I will show you how I take existing working code, a RESTFUL API using Node.js Express and Sequelize against a Postgres database and create monstarillo templates that will work against any sequelize model.. The code for this simple API can be found at https://github.com/mrpatrickwright/simple-node-sequelize-api. The API runs against the Chinhook sample database. You can learn how to stand it up in docker here
The final templates for created for this post can be found at https://github.com/mrpatrickwright/simple-node-sequelize-api-templates.
The first step in writing templates to generate an API is to decide what code you want to generate. The first step would be to write an API that you like or to find an existing API that you like. When generating API code it is important to know your database. If your database tables all have a single primary key your templates will be much simpler. Writing code that works against tables with composite primary keys is different and often more complicated. You will want to make sure that your sample code has example of models with single and composite primary keys if you have models that have composite primary keys. In my simple API I have created two controllers. The artistController works against the Artist model. The Artist model works against the artist table that has a single primary key.
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Artist', {
artistId: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
field: 'ArtistId'
},
name: {
type: DataTypes.STRING(120),
allowNull: true,
field: 'Name'
}
}, {
sequelize,
tableName: 'Artist',
schema: 'public',
timestamps: false,
indexes: [
{
name: "PK_Artist",
unique: true,
fields: [
{ name: "ArtistId" },
]
},
]
});
};
The playlistTrackController works against the PlaylistTrack model. The PlaylistTrack model works against the PlaylistTrack table and has a composite primary key
const Sequelize = require('sequelize');
module.exports = function(sequelize, DataTypes) {
return sequelize.define('PlaylistTrack', {
playlistId: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
references: {
model: 'Playlist',
key: 'PlaylistId'
},
field: 'PlaylistId'
},
trackId: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
references: {
model: 'Track',
key: 'TrackId'
},
field: 'TrackId'
}
}, {
sequelize,
tableName: 'PlaylistTrack',
schema: 'public',
timestamps: false,
indexes: [
{
name: "IFK_PlaylistTrackTrackId",
fields: [
{ name: "TrackId" },
]
},
{
name: "PK_PlaylistTrack",
unique: true,
fields: [
{ name: "PlaylistId" },
{ name: "TrackId" },
]
},
]
});
};
Features of the Example API
The config/config.js and config/database.js files support changing the connection string used by sequelize for different environments (development, test and production) via environment variables. In development these values can be set in the .env file in the root of the project.
The utils/appError.js, utils/catchAsync.js and controller/errorController.js files support error handling and reporting.
I think the rest is pretty straight forward. Controllers are in the controller folder. Routes are in the route folder and .sequelizerc is included to configure sequelize.
Create a Starting template.json File using Monstarillo Import
The set of templates that we create will have a number of files that do not need to be modified or will only require a little modification. Monstarillo’s import function will help me with this. The import function allows me to point monstarillo at a folder copy all of the files to a new folder and create a template.json file that when run will generate the same files and file structure. Monstarillo import has four flags:
- –iFiles – A list of files to ignore
- –iFolders – A list of folders to ignore
- –sFolder – Source folder. The folder to copy from
- –tFolder – Template folder. The folder to copy to
I will run monstarillo import and ignore the models and node_modules folders with the following command:
monstarillo import --sFolder <my source folder> --iFolders 'models,node_modules' --tFolder <my destination folder>
In your destination folder you will have a copy of all of the files from your source folder that were not ignored. You will also have a template.json file. If we ran monstarillo against the templates the json file it would generate all of the files from your source folder that were not ignored. We should be able to do an npm install in our new project and have a working API. Take a look ath the template.json. Notice that copyOnly property of each template is set to true in the template.json. With the copy only flag set to true monstarillo will not process the files as a template.
Generate a New Project and Get it Running
The next step is to run monstarillo against our templates to create a new project for us to run. The models.json file is created from and existing sequelize model. You can learn how to Export Sequelize Model Data For Use With Monstarillo
monstarillo js-orm \
--m <path to models.json > \
--t <path to template.json >
Our final template set will work against an existing sequelize model. I will copy the db folder from my source folder which includes the models into my new project folder. We should delete the package-lock.json file

Next I will run npm install to install all of the JavaScript dependencies. Then I will run npm start:dev to run the project
npm install
npm start:dev
The project runs successfully and I can test a few endpoints( localhost:3000/api/v1/playlistTracks localhost:3000/api/v1/playlistTracks/playlistId/1/trackId/3393).

Plan the Templates
The templates will be created in three steps. I will need to write templates to create the controllers and routes. I will also need to generate the app.js so I can reference all of the routes I create. To do this I will create the three templates one at a time in an effort to debug one template at at time. The order that I create the templates in matters. In this case I will generate the controllers first. If I tried to generate the routes first I could not run the project because the controllers the routes referenced would not exist. If I tried to generate the app.js first I could not run the project because the routes would not exist.
I will work on the controller template first. I will work on generating code that works on tables with a single primary key first so I look at the artistController.js. From a template standpoint it consists of three parts. In the first part some variables are initialized. This section will be the same for all of our models.
const sequelize = require('../config/database');
var initModels = require("../db/models/init-models");
models = initModels(sequelize);
const AppError = require('../utils/appError');
const catchAsync = require('../utils/catchAsync');
The second section is five methods that will need to be customized for each model.
const getAllArtist = catchAsync(async (req, res, next) => {
const result = await models.Artist.findAll();
return res.json({
status: 'success',
data: result,
});
});
......
const createArtist = catchAsync(async (req, res, next) => {
const body = req.body;
const newArtist = await models.Artist.create({
artistId: body.artistId,
name: body.name,
});
return res.status(201).json({
status: 'success',
data: newArtist,
});
});
The final section exports the methods from the second section. This section will need to be customized for each model.
Create the controller.tmpl
In your template project create a controller.tmpl in the controller folder and delete the artistController.js . I need to remove the artistController.js from the template.json because I will be generating this file with the controller.tmpl and I no longer want to copy them over when I run monstarillo.
I need to add the controller.tmpl to the template.json so monstarillo will generate the controllers
{
"templateFile": "{{getTag .Tags \"TemplateRoot\"}}/controller/controller.tmpl",
"generatedFileName": "{{ .CurrentModel.GetModelNameInCase \"camel\"}}Controller.js",
"generatedFolderName": "/controller/",
"minimumGeneratedFileLength": 0,
"outputPath": "{{getTag .Tags \"OutputPath\"}}",
"overwriteFile": true,
"copyOnly": false
}
Notice that I use the GetModelNameInCase method to create the controller file name.
Next I copy the code from the artistController.js to the controller.tmpl. When needed I will replace occurrences of “Artist” with:
{{.CurrentModel.GetModelNameInCase "pascal"}}
In this case “Artist” sometimes refers to the model name. The string “Artist” is also found in the string ArtistId which is a column name. We cannot blindly do a search and replace.
When needed I replace occurrences of artist with:
{{.GetColumnNameInCase "camel"}}
When Accessing column properties I the code needs to be wrapped inside of a range statement to get access to the column data. I can loop through all columns or just the models columns that are a part of the model’s primary key.
{{range .CurrentModel.GetPrimaryModelColumns}}
# code referencing column data
{{end}}
or
{{range .CurrentModel.Columns}}
# code referencing column data
{{end}}
While I am inside of a range block I cannot access .CurrentModel to get the model name. To get around this I create a variable $ModelName outside of the range block and us the variable inside of the range block when I need to use the model name.
{{$ModelName := .CurrentModel.GetModelNameInCase "pascal"}}
const get{{.CurrentModel.GetModelNameInCase "pascal"}}ById = catchAsync(async (req, res, next) => {
{{range .CurrentModel.GetPrimaryModelColumns}}
const {{.GetColumnNameInCase "camel"}} = req.params.{{.GetColumnNameInCase "camel"}};
const result = await models.{{$ModelName}}.findByPk({{.GetColumnNameInCase "camel"}});{{end}}
if (!result) {
return next(new AppError('Invalid {{.CurrentModel.GetModelNameInCase "camel"}} id', 400));
}
return res.json({
status: 'success',
data: result,
});
});
In the Update method I need to create properties on the result object for all of the columns that are not a part of the primary key
# from updateArtist method
result.name = body.name;
To do this I create a range block that includes all columns. I add a if block to filter on columns where IsPrimaryKey is false
{{range .CurrentModel.Columns}}{{if eq .IsPrimaryKey false}}
result.{{.GetColumnNameInCase "camel"}} = body.{{.GetColumnNameInCase "camel"}};{{end}}{{end}}
Test the controller.tmpl
When I am ready to test the template I run monstarillo again to update my new project with freshly generated code and run the project. I test all of the endpoints on the artistController. None of the other controllers will work yet because we have not created routes for them and have not modified app.js to use the new routes.
The next step is to test another controller. I want to pick a controller that runs against a model that can a single primary key like the albumController. To do this I need to create a albumRoute.js in the route folder. The albumRoute.js code is below:
const {
createAlbum,
getAllAlbum,
getAlbumById,
updateAlbum,
deleteAlbum,
} = require('../controller/AlbumController');
const router = require('express').Router();
router
.route('/')
.get( getAllAlbum)
.post(createAlbum);
router
.route('/:albumId')
.get( getAlbumById)
.patch( updateAlbum)
.delete( deleteAlbum)
module.exports = router;
I will also need to update the app.js to use the new route. I have added the two bolded lines in the code below.
require('dotenv').config({ path: `${process.cwd()}/.env` });
const express = require('express');
const artistRouter = require('./route/artistRoute');
const albumRouter = require('./route/albumRoute');
const playlistTrackRouter = require('./route/playlistTrackRoute');
const catchAsync = require('./utils/catchAsync');
const AppError = require('./utils/appError');
const globalErrorHandler = require('./controller/errorController');
const app = express();
app.use(express.json());
// all routes will be here
app.use('/api/v1/artists', artistRouter);
app.use('/api/v1/albums', albumRouter);
app.use('/api/v1/playlistTracks', playlistTrackRouter);
app.use(
'*',
catchAsync(async (req, res, next) => {
throw new AppError(`Can't find ${req.originalUrl} on this server`, 404);
})
);
app.use(globalErrorHandler);
const PORT = process.env.APP_PORT || 4000;
app.listen(PORT, () => {
console.log('Server up and running', PORT);
});
If I run monstarillo again to generate code it will write over the app.js. To prevent this I will update the template.json file and set the overwriteFile property of the app.js template to false.
{
"templateFile": "{{getTag .Tags \"TemplateRoot\"}}/app.js",
"generatedFileName": "app.js",
"generatedFolderName": "/",
"minimumGeneratedFileLength": 0,
"outputPath": "{{getTag .Tags \"OutputPath\"}}",
"overwriteFile": false, "copyOnly": true
}
I will test all of the endpoints of the albumController. Once I am satisfied I will work on a template to create the routes for the API.
Create the route.tmpl
Next I will create the route.tmpl file and add a template to my template.json to reference it. My new template entry will be:
{
"templateFile": "{{getTag .Tags \"TemplateRoot\"}}/route.tmpl",
"generatedFileName": "{{ .CurrentModel.GetModelNameInCase \"camel\"}}Route.js",
"generatedFolderName": "/route/",
"minimumGeneratedFileLength": 0,
"outputPath": "{{getTag .Tags \"OutputPath\"}}",
"overwriteFile": true,
"copyOnly": false
}
This will create a route for every model in the route folder. Once again I will begin by copying code from the artistRoute.js into my new template and make the necessary changes to it. This is pretty simple, the text “Artist” can be replaced with a call to GetModelNameInCase “pascal”. The “artist” in the require path can be replaced with GetModelNameInCase “camel”. The “artistId” in the .route parameter can be generated in a range block referencing the model’s primary key. The new route.tmpl will be :
const {
create{{.CurrentModel.GetModelNameInCase "pascal"}},
getAll{{.CurrentModel.GetModelNameInCase "pascal"}},
get{{.CurrentModel.GetModelNameInCase "pascal"}}ById,
update{{.CurrentModel.GetModelNameInCase "pascal"}},
delete{{.CurrentModel.GetModelNameInCase "pascal"}},
} = require('../controller/{{.CurrentModel.GetModelNameInCase "camel"}}Controller');
const router = require('express').Router();
router
.route('/')
.get( getAll{{.CurrentModel.GetModelNameInCase "pascal"}})
.post(create{{.CurrentModel.GetModelNameInCase "pascal"}});
router
{{range .CurrentModel.GetPrimaryModelColumns}}.route('/:{{.GetColumnNameInCase "camel"}}'){{end}}
.get( get{{.CurrentModel.GetModelNameInCase "pascal"}}ById)
.patch( update{{.CurrentModel.GetModelNameInCase "pascal"}})
.delete( delete{{.CurrentModel.GetModelNameInCase "pascal"}})
module.exports = router;
Test the route.tmpl
Run monstarillo again go generate the routes. You will notice routes have been generated in the routes folder.

Next I will test the artist and album endpoints again.
Update the app.js to Use Our New Routes
The next step is to update the app.js to use our newly created routes. Rename app.js to app.tmpl. Update the template reference in template.json to reference the new name. In the template’s properties set overwriteFile to true and copyOnly to false. The template reference should now be:
{
"templateFile": "{{getTag .Tags \"TemplateRoot\"}}/app.tmpl",
"generatedFileName": "app.js",
"generatedFolderName": "/",
"minimumGeneratedFileLength": 0,
"outputPath": "{{getTag .Tags \"OutputPath\"}}",
"overwriteFile": true,
"copyOnly": false
}
We have two sections of the code to modify in our template. We need to create a variable for each of our routes.
const artistRouter = require('./route/artistRoute');
const playlistTrackRouter = require('./route/playlistTrackRoute');
And we need to use each of our routes.
app.use('/api/v1/artists', artistRouter);
app.use('/api/v1/playlistTracks', playlistTrackRouter);
For the first section of the code we will create a range block over our models.
{{range .Models}}
const {{.GetModelNameInCase "camel"}}Router = require('./route/{{.GetModelNameInCase "camel"}}Route');{{end}}
Using the .Models object we can loop through all of our models. Monstarillo runs each template once for each model. The .CurrentModel object will not work here because it only holds one model. When we generate the code our generated code will only have data from the last model monstarillo ran against.
The second bit of code will be similar.
// routes {{range .Models}}
app.use('/api/v1/{{.GetModelNamePluralInCase "camel"}}', {{.GetModelNameInCase "camel"}}Router);{{end}}
In this bit of our template we will use the .GetModelNamePluralInCase property of our model to return the pluralized model name.
The entire app.tmpl is below:
require('dotenv').config({ path: `${process.cwd()}/.env` });
const express = require('express');
{{range .Models}}
const {{.GetModelNameInCase "camel"}}Router = require('./route/{{.GetModelNameInCase "camel"}}Route');{{end}}
const catchAsync = require('./utils/catchAsync');
const AppError = require('./utils/appError');
const globalErrorHandler = require('./controller/errorController');
const app = express();
app.use(express.json());
// routes {{range .Models}}
app.use('/api/v1/{{.GetModelNamePluralInCase "camel"}}', {{.GetModelNameInCase "camel"}}Router);{{end}}
app.use(
'*',
catchAsync(async (req, res, next) => {
throw new AppError(`Can't find ${req.originalUrl} on this server`, 404);
})
);
app.use(globalErrorHandler);
const PORT = process.env.APP_PORT || 4000;
app.listen(PORT, () => {
console.log('Server up and running', PORT);
});
Next we can test the album and artist controller endpoints. All of the endpoints for models with a single primary key should be working now.
Modify The Controller Template to Work With Models That Have Composite Primary Keys
When reviewing the controller code I see that there is a difference caused by the composite primary key in the getPlaylistTrackById method. This method in the single primary key code calls the model’s findByPk method passing a single parameter, the single primary key. This method in the composite primary key code calls the model’s findOne method and passes a variable for each of the models primary key columns. There is a similar difference in the update and delete methods. The composite primary key code needs to pass to parameters to the where clauses in these methods, one for each of the model’s primary key columns.
Delete the playlistTrackController from the template.json and also delete the file from the template project so it does not clutter it up. We will be using the playlistTrackController that the controller.tmpl generates from now on.
To accomplish these code changes we will utilize the model’s HasPrimaryKey property. We will wrap the code that generates the offending methods in an if block. One block will execute for models with a single primary key and another block will execute for models with a composite primary key.
{{if eq .CurrentModel.HasCompositePrimaryKey true}}
// composite primary key code
{{else}}
// single primary key code
{{else}}
Our new controller template will be :
const sequelize = require('../config/database');
var initModels = require("../db/models/init-models");
models = initModels(sequelize);
const AppError = require('../utils/appError');
const catchAsync = require('../utils/catchAsync');
const getAll{{.CurrentModel.GetModelNameInCase "pascal"}} = catchAsync(async (req, res, next) => {
const result = await models.{{.CurrentModel.GetModelNameInCase "pascal"}}.findAll();
return res.json({
status: 'success',
data: result,
});
});
{{$ModelName := .CurrentModel.GetModelNameInCase "pascal"}}
{{if eq .CurrentModel.HasCompositePrimaryKey true}}
const get{{.CurrentModel.GetModelNameInCase "pascal"}}ById = catchAsync(async (req, res, next) => {
{{range .CurrentModel.GetPrimaryModelColumns}}
const {{.GetColumnNameInCase "camel"}} = req.params.{{.GetColumnNameInCase "camel"}};{{end}}
const result = await models.{{$ModelName}}.findOne({
where: { {{range .CurrentModel.GetPrimaryModelColumns}}
{{.GetColumnNameInCase "camel"}}:{{.GetColumnNameInCase "camel"}},{{end}}
}
});
if (!result) {
return next(new AppError('Invalid {{.CurrentModel.GetModelNameInCase "camel"}} id', 400));
}
return res.json({
status: 'success',
data: result,
});
});
{{else}}
const get{{.CurrentModel.GetModelNameInCase "pascal"}}ById = catchAsync(async (req, res, next) => {
{{range .CurrentModel.GetPrimaryModelColumns}}
const {{.GetColumnNameInCase "camel"}} = req.params.{{.GetColumnNameInCase "camel"}};
const result = await models.{{$ModelName}}.findByPk({{.GetColumnNameInCase "camel"}});{{end}}
if (!result) {
return next(new AppError('Invalid {{.CurrentModel.GetModelNameInCase "camel"}} id', 400));
}
return res.json({
status: 'success',
data: result,
});
});
{{end}}
{{if eq .CurrentModel.HasCompositePrimaryKey true}}
const update{{.CurrentModel.GetModelNameInCase "pascal"}} = catchAsync(async (req, res, next) => {
{{range .CurrentModel.GetPrimaryModelColumns}}
const {{.GetColumnNameInCase "camel"}} = req.params.{{.GetColumnNameInCase "camel"}};{{end}}
const body = req.body;
const result = await models.{{.CurrentModel.GetModelNameInCase "pascal"}}.findOne({
where: { {{range .CurrentModel.GetPrimaryModelColumns}}
{{.GetColumnNameInCase "camel"}}: {{.GetColumnNameInCase "camel"}},{{end}}
}
});
if (!result) {
return next(new AppError('Invalid {{.CurrentModel.GetModelNameInCase "camel"}} id', 400));
}
{{range .CurrentModel.Columns}}{{if eq .IsPrimaryKey false}}
result.{{.GetColumnNameInCase "camel"}} = body.{{.GetColumnNameInCase "camel"}};{{end}}{{end}}
const updatedResult = await result.save();
return res.json({
status: 'success',
data: updatedResult,
});
});
{{else}}
const update{{.CurrentModel.GetModelNameInCase "pascal"}} = catchAsync(async (req, res, next) => {
{{range .CurrentModel.GetPrimaryModelColumns}}
const {{.GetColumnNameInCase "camel"}} = req.params.{{.GetColumnNameInCase "camel"}};{{end}}
const body = req.body;
const result = await models.{{.CurrentModel.GetModelNameInCase "pascal"}}.findOne({ {{range .CurrentModel.GetPrimaryModelColumns}}
where: { {{.GetColumnNameInCase "camel"}}: {{.GetColumnNameInCase "camel"}} },{{end}}
});
if (!result) {
return next(new AppError('Invalid {{.CurrentModel.GetModelNameInCase "camel"}} id', 400));
}
{{range .CurrentModel.Columns}}{{if eq .IsPrimaryKey false}}
result.{{.GetColumnNameInCase "camel"}} = body.{{.GetColumnNameInCase "camel"}};{{end}}{{end}}
const updatedResult = await result.save();
return res.json({
status: 'success',
data: updatedResult,
});
});
{{end}}
{{if eq .CurrentModel.HasCompositePrimaryKey true}}
const delete{{.CurrentModel.GetModelNameInCase "pascal"}} = catchAsync(async (req, res, next) => {
{{range .CurrentModel.GetPrimaryModelColumns}}
const {{.GetColumnNameInCase "camel"}} = req.params.{{.GetColumnNameInCase "camel"}} ;{{end}}
const result = await models.{{.CurrentModel.GetModelNameInCase "pascal"}}.findOne({
where: { {{range .CurrentModel.GetPrimaryModelColumns}}
{{.GetColumnNameInCase "camel"}}: {{.GetColumnNameInCase "camel"}},{{end}}
}
});
if (!result) {
return next(new AppError('Invalid {{.CurrentModel.GetModelNameInCase "camel"}}Id', 400));
}
await result.destroy();
return res.json({
status: 'success',
message: 'Record deleted successfully',
});
});
{{else}}
const delete{{.CurrentModel.GetModelNameInCase "pascal"}} = catchAsync(async (req, res, next) => {
{{range .CurrentModel.GetPrimaryModelColumns}}
const {{.GetColumnNameInCase "camel"}} = req.params.{{.GetColumnNameInCase "camel"}} ;{{end}}
const result = await models.{{.CurrentModel.GetModelNameInCase "pascal"}}.findOne({
{{range .CurrentModel.GetPrimaryModelColumns}}
where: { {{.GetColumnNameInCase "camel"}}: {{.GetColumnNameInCase "camel"}} },{{end}}
});
if (!result) {
return next(new AppError('Invalid {{.CurrentModel.GetModelNameInCase "camel"}}Id', 400));
}
await result.destroy();
return res.json({
status: 'success',
message: 'Record deleted successfully',
});
});
{{end}}
const create{{.CurrentModel.GetModelNameInCase "pascal"}} = catchAsync(async (req, res, next) => {
const body = req.body;
const new{{.CurrentModel.GetModelNameInCase "pascal"}} = await models.{{.CurrentModel.GetModelNameInCase "pascal"}}.create({
{{range .CurrentModel.Columns}}
{{.GetColumnNameInCase "camel"}}: body.{{.GetColumnNameInCase "camel"}}, {{end}}
});
return res.status(201).json({
status: 'success',
data: new{{.CurrentModel.GetModelNameInCase "pascal"}},
});
});
module.exports = {
getAll{{.CurrentModel.GetModelNameInCase "pascal"}},
get{{.CurrentModel.GetModelNameInCase "pascal"}}ById,
update{{.CurrentModel.GetModelNameInCase "pascal"}},
create{{.CurrentModel.GetModelNameInCase "pascal"}},
delete{{.CurrentModel.GetModelNameInCase "pascal"}}
}
Modify The Route Template to Work With Models That Have Composite Primary Keys
The routes for models that have a composite primary key differ from routes with a single primary key. Looking the code the difference is in the route that is created to select a single record. The code differences are below:
// single primary key
.route('/:artistId')
vs
// composite primary key
.route('/playlistId/:playlistId/trackId/:trackId')
The solution to this is pretty simple. When we have a composite primary key we need to loop through the primary keys to create the routes. The modified route.tmpl is below:
const {
create{{.CurrentModel.GetModelNameInCase "pascal"}},
getAll{{.CurrentModel.GetModelNameInCase "pascal"}},
get{{.CurrentModel.GetModelNameInCase "pascal"}}ById,
update{{.CurrentModel.GetModelNameInCase "pascal"}},
delete{{.CurrentModel.GetModelNameInCase "pascal"}},
} = require('../controller/{{.CurrentModel.GetModelNameInCase "camel"}}Controller');
const router = require('express').Router();
router
.route('/')
.get( getAll{{.CurrentModel.GetModelNameInCase "pascal"}})
.post(create{{.CurrentModel.GetModelNameInCase "pascal"}});
router{{if eq .CurrentModel.HasCompositePrimaryKey true}}
.route('{{range .CurrentModel.GetPrimaryModelColumns}}/{{.GetColumnNameInCase "camel"}}/:{{.GetColumnNameInCase "camel"}}{{end}}')
{{else}}
{{range .CurrentModel.GetPrimaryModelColumns}}.route('/:{{.GetColumnNameInCase "camel"}}'){{end}}{{end}}
.get( get{{.CurrentModel.GetModelNameInCase "pascal"}}ById)
.patch( update{{.CurrentModel.GetModelNameInCase "pascal"}})
.delete( delete{{.CurrentModel.GetModelNameInCase "pascal"}})
module.exports = router;
Now we can test the playlistTrack endpoints.
We have a little cleaning up to do in our template.json file. If you have not already you should remove the package-lock.json, artistRoute.js, playistTrackRoute.js artistController.js and playlistTrackController.js entries from the template.json because we don’t want to include them in applications we generate going forward. We should also delete the files from our project folders as they are no longer being used.
Conclusion
You have now learned how to create monstarillo templates that generates code that you like. You learned how to use monstarillo’s import feature to kick start the template writing process. You learned how to work on templates one at a time to make the debugging process easier. You learned how to make a single template work for models with one primary key and composite primary keys. And learned some of the coding patterns used to write monstarillo templates against ORM models.
The final templates for created for this post can be found at https://github.com/mrpatrickwright/simple-node-sequelize-api-templates.