This article is a tutorial on code generation and automatic api creation using sugar-generate which is free and open source on github and npm.
I feel a little stupid. I’ve been writing the same API code and boilerplate for years and it didn’t occur to me to write code that writes this code for me. But now that I’ve started using code generators, I’m never going back. We’ve gone from assembly to C to …Javascript. It’s clear the future will be about writing less code to do more.
I hadn’t deeply explored code generation but my quick search took me to two places:
Swagger Codegen
https://swagger.io/tools/swagger-codegen/
This looks promising!! Except when you actually look at what it generates. I don’t want server stubs. I want it to take a schema and generate the damn thing for me.
Strapi
Strapi is a pretty cool choice. No complaints here. I just am wary of buying into large frameworks like this. When shit hits the fan, I don’t know where to go to fix it.
Also the second I tried to put my own local mongodb database in, the app crashed and I couldn’t get it to recover. Probably something simple but annoying none the less.
Both options are fine, obviously, a lot of people find these two modules useful. I wanted to do something a little different. Something a little lighter, and something that would focus on my productivity.
Sugar Generate
Let’s get started with sugar-generate. It’s a free and open source NPM module to generate CRUD APIs so you don’t have to.
Prerequisites
- Have MongoDB installed and running
brew install mongodb && sudo mongod --bind_ip_all
The — bind_ip_all will be important when trying to connect from docker.
Then install the sugar-generate node module.
npm i -g sugar-generate
Create a schema. Schema’s are JSON representations of your APIs. Below is a simple one for an API called Monkey
{
"schema": {
"first_name": {
"type": "String",
"default": ""
},
"last_name": {
"type": "String",
"default": ""
},
"isDead": {
"type": "Boolean",
"default": false
},
"age": {
"type": "Number",
"default": false
}
},
"statics": {}
}
We will continue with the Monkey schema but here is an example of a slightly more complex schema that sugar-generate supports
{
"schema": {
"first_name": {
"type": "String",
"default": ""
},
"last_name": {
"type": "String",
"default": ""
},
"email": {
"type": "String",
"trim": true,
"required": true,
"unique": true,
"immutable": true
},
"password": {
"type": "String",
"trim": true,
"select": false,
"immutable": true
},
"intro": {
"type": "Boolean",
"default": false
},
"team": {
"type": "ObjectId",
"ref": "Team"
},
"sub": {
"one": {
"type": "String",
"trim": true,
"required": true
},
"two": {
"type": "Number",
"required": true
}
},
"role": {
"type": "String",
"enum": ["user", "maker"],
"default": "user"
}
},
"statics": {
"statuses": ["created", "under_review", "listed", "deleted"],
"status": {
"active": "active",
"inactive": "inactive",
"deleted": "deleted"
}
}
}
Under the hood, we’re using mongoose so the schemas are designed to mimic the mongoose schema representation. You can see this in the generated code.
const database = require('../connection/mongo');
const {
Schema
} = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');
const schema = new Schema({
first_name: {
"type": String,
"default": ""
},
last_name: {
"type": String,
"default": ""
},
isDead: {
"type": Boolean,
"default": false
},
age: {
"type": Number,
"default": false
},
}, {
timestamps: true
});
schema.plugin(mongoosePaginate);
module.exports = database.model("monkey", schema);
Now on to the fun part.
sugar-generate \
--type api \
--name monkey \
--schema /path/to/monkey.json \
--destination /where/you/want/the/code
and now you have your generated API! Let’s take a look at the output.
We have a simple config file containing out database connection, port, and a few other items I plan to add in the future.
our connections folder holds our database connection.
Our controllers hold our single endpoint with create, get, update, and delete functions.
Our mongoose model sits here
Super simple routing
You love tests don’t you.
User-can is some magic I want to implement in the future. The idea is that a simple middleware layer will extend common functionality like authentication for people and machines.
the rest of this crap I'm sure you’re intimately familiar with. Only one thing to point out here is the swagger.json. When you start the API, this file gets loaded in the Swagger UI.
Obligatory NPM install
npm i
Let’s run the test suite to make sure the generator worked properly.
npm run test
Looking good! Let’s fire up the server! Generate the swagger doc, and start the server
npm run docs && npm start
and now navigate to localhost:7777
and here you have your ready-to-deploy api.
… and how about deployment? We got you covered there too
Build the docker container.
We’re going to have to make one small change to the configs/config.json file
// in config/config.json
// change mongoURL to mongodb://<your ip address>:27017/sugardocker build -t monkeys:0.0.1 .
Now run it!
docker run -ti -p 3030:3030 -e PORT=3030 monkeys:0.0.1
if you get the “Mongo OKAY” message you’re connected and ready to rock and roll.
If you’re looking to get it on the internet, try Google Cloud Run. It’s my new favorite services from Google (no affiliation). Just push this docker container to google and have it up and running in no time on the web with an SSL cert. It’s also a scale to zero “serverless” service so you’ll have infinite scalability all the way down to zero without any ops effort or maintenance 🤗🤗.
docker build -t gcr.io/<your google project id>/monkeys .
docker push gcr.io/<your google project id>/monkeys
now let’s hop into the google console cloud run service!
select create service
on the next page click the “select” button in the container image URL
now let’s pick our container we pushed
name the service and hit create!
after about 30 seconds you’ll have the new api deployed online over ssl
Hit the new url and voila! You’ll have to change the swagger.json to the new address. Make that change and hit “Explore” in the swagger ui.
You can probably see now why I feel a little behind the curve. I now no longer have to
- write monkey code
- write stupid boilerplate
- write docker files
- write lets encrypt crons
- set up a server
- manage the server
- get phone calls at 2 am because the service is down
- worry about cost (it spins up and down with usage)
- get yelled at for spinning up 30 c5xl because of Christmas load anticipation
and I get to do all that in about a minute (once you’ve done it a few times)
So what’s the catch? Primarily it’s that the project is young.
Here’s a bit more about the project
Features 🙉
- Generates simple Nodejs code
- Uses Mongodb with Mongoose ORM
- Easy to build / deploy / maintain
- Dockerfile included
- Generates CRUD APIs
— create
— get (many, with pagination; supports search, sort, filter, pagination out of the box)
— getOne
— update
— delete
What it’s good at 🙊
- Generating an initial API
- Microservice oriented
- Ready to deploy (build with docker => deploy)
What it’s not good at (yet) 🙈
- idempotent changes (i.e. it doesn’t know if you wrote code in there or changed things around)
- working with modified code
- populating table joins
- custom actions inside controller functions
I’m building all kinds of fun stuff over at SugarKubes. Let me know what you’re building with sugar-generate and what features you’d like to see!
Hugs,
AP