On Code Generators

Developers underestimate the power of code generators.

We don’t always need more abstraction in the form of libraries, frameworks, or software products. When we are used to coding features a certain way, it isn’t always in our best interest to make things simpler in the long run at the expense of a steep learning curve that will decrease your productivity here and now. Simple scripts can automate repetitive tasks without you having to reinvent your workflow.

Writing RESTful APIs is a striking example. 

We could easily buy no-code tools to do the job for us, but we would decrease our profits, be forced to learn a new tool, suffer vendor lock-in, and sacrifice our customization capabilities. More importantly, the abstraction layer would prevent us from fine-tuning our code to better serve the business logic, and a limited programming interface could forbid us from integrating the tool in an automation pipeline.

What if we could generate web API code without having to even think about it instead? A web API is simply a proxy between an application and a database, so we can generate code to do just that simply by knowing the tables and the different fields. Each table can be formalized as an interoperable JSON schema file, for example:

tables.json

{
  "name": "Comment",
  "fields": {
      "id": {"type": "int"},
      "text_fk": {"type": "Text"},
      "user_fk": {"type": "WritelierUser"},
      "content": {"type": "longtext"}
  }
}

The stakeholders can agree on code convention beforehand and write a reusable template for each table and data access layer:

crud_service.ejs

module.exports = {
  readOne: (args) => {        
      return SELECT * FROM <%=table%> WHERE id='${args.id}'
  }
}

api_router.ejs

import express from 'express';
import mysql from '../../service/utils/mysql';

var <%=table.toLowerCase()%>_service = require('../../service/crud/<%=table.toLowerCase()%>')

let router = express.Router();

router.get('/:id', (req, res, next) => {
  mysql.query(<%=table.toLowerCase()%>_service.readOne(req.params.id))
  .then(result => {
    res.send({ok: true, <%=table.toLowerCase()%>: result})
  })
})

We can then simply copy/paste the resulting code as needed in a server.

controller/api/comment.js

import express from 'express';
import mysql from '../../service/utils/mysql';

var comment_service = require('../../service/crud/comment_service')

let router = express.Router();

router.get('/:id', (req, res, next) => {
  mysql.query(comment_service.readOne(req.params.id))
  .then(result => {
    res.send({ok: true, comment: result})
  })
}) 

The code can still be adapted to serve specific requirements in one fell swoop (you could, for example, want to combine MySQL statements to avoid sending more HTTP requests), and it didn’t cost a thing.

We can also expand the generator to take into account the entire tech stack. For example, we could write a template to render standard React components from the table definition. It could be useful to quickly go through stored data from a back-office application. 

If you write 60 lines of code on a good day, having the possibility to instantly output thousands of lines is a non-negligible productivity boost. The sky is the limit!