DEV Community

High performance school - blog
High performance school - blog

Posted on • Updated on

Saving time when starting a project

clock on the plate

How much time do you spend setting up the basis of a new project?

No matter how simple or complex is your service, we have to build the base, that basic base:

  • Create entities
  • Create the folder structure
  • Then create some repositories
  • Some usecases/services for each entity
  • Configure the api, routes, midlewares
  • etc

Come oooooooonnnn, who likes to do this?

How to save time configuring the base's project?

Today we will create a Postgres-based api with two presentation layers: Graphql and REST in less then 1 minute.

For today's dish, we use the ingredients:

To start our "fast project" we need first of all to modeling our base. Imagine we are creating an e-commerce backend app, in an e-commerce we would have 3 entities: user, product and sale.

black boy thinking

The first step to build our project will be to create the entities using the @herbs/herbs lib that will provide some metadata to build everything else based in our entities: usecases, repositories, migrations, graphql, rest etc.

Building an entity with Herbs is very simple, just import the entity module and pass the parameters entity("entityName", { fields }).

All our entities will be inside a folder /entities

// entities/customer.js
const { entity, field } = require('@herbsjs/herbs')


const Customer = entity('Customer', {
    id: field(Number),
    cellphone: field(String),
    email: field(String),
    gender: field(String),
    age: field(Number),
    discountClub: field(Boolean),
    city: field(String),
    state: field(String),
    address: field(String),
    zipcode: field(String),
    lastName: field(String),
    fisrtName: field(String),
    civilDocument: field(String),
})

module.exports = Customer
Enter fullscreen mode Exit fullscreen mode

After create our simple entity, what about add some validations? To do this with Herbs is very simple too, just add an option object with some rules as second parameter field(Type, { options }).

If you'd like to see more about validation types, you can check out the Herbs documentation here.

// entities/customer.js
const { entity, field } = require('@herbsjs/herbs')

const requiredString = {
    validation: {
        presence: true,
        length: { minimum: 3, maximum: 255 },
    }
}

const Customer = entity('Customer', {
    id: field(Number, {
        // Optional option
        validation: {
            presence: true,
            numericality: {
                greaterThan: 0,
                onlyInteger: true
            }
        }
    }),
    cellphone: field(String, {
        validation: {
            format: /^\+[1-9]{1}[0-9]{3,14}$/
        }
    }),
    email: field(String, {
        validation: { ...requiredString.validation, email: true } 
    }),
    gender: field(String, {
        validation: { ...requiredString.validation, contains: { allowed: ["M", "W", "X"] } } 
    }),
    age: field(Number),
    discountClub: field(Boolean),
    city: field(String, requiredString),
    state: field(String, requiredString),
    address: field(String, requiredString),
    zipcode: field(String, requiredString),
    lastName: field(String, requiredString),
    fisrtName: field(String, requiredString),
    civilDocument: field(String, requiredString),
})

module.exports = Customer
Enter fullscreen mode Exit fullscreen mode
// entities/product.js
const { entity, field } = require('@herbsjs/herbs')

const required =  { validation: { presence: true } }
const requiredString = {
    validation: {
        ...required.validation,
        length: { minimum: 3, maximum: 255 },
    }
}

module.exports = entity('Product', {
    id: field(Number, {
        // Optional option
        validation: {
            presence: true,
            numericality: {
                greaterThan: 0,
                onlyInteger: true
            }
        }
    }),
    limitDiscount: field(Number, { 
        validation: { 
            greaterThan: 0,
            lessThan: 100,
            onlyInteger: true 
        }
    }),
    size:  field(String, required),
    stock: field(Number, required),
    color: field(String, required),
    name: field(String, requiredString),
    brand: field(String, requiredString),
    category: field(String, requiredString),
    description: field(String, requiredString),
    price: field(Number, { validation: { presence: true, greaterThan: 0 } }),
})
Enter fullscreen mode Exit fullscreen mode
// entities/sale.js
const { entity, field } = require('@herbsjs/herbs')

const required =  { validation: { presence: true } }

module.exports = entity('Sale', {
    id: field(Number, {
        // Optional option
        validation: {
            presence: true,
            numericality: {
                greaterThan: 0,
                onlyInteger: true
            }
        }
    }),
    productID: field(Number, required),
    customerID: field(Number, required),
    total: field(Number, required),
    quantity: field(Number, required)
})
Enter fullscreen mode Exit fullscreen mode

In the end we will have this result:
Alt Text

generating the project

Finally our dreamed come to be true, where we generated our project based on our entities!

amazing jack

The Herbs CLI was created to help you create and maintain your project in a faster way when you uses the entire Herbs ecosystem.

When we type herbs new herbs will ask a some things about your project to generate our project.

cli options

Note the last question is an optional question, if you don't specify your entities folder path, the project will be generated based on a default “User” entity, but we're not here to generate a “boilerplate” are we? So let's fill it in with out entities folder path.

When you press Enter our app will be generated and you will find a folder with the project name.

Generated folder structure

look this! How much automatically generated stuff! How much time we saved here! Very satisfying! Note that the entire project is done: migrations, repositories, api(graphl and rest) create, read, update and delete usecases and everything else!

wow gif

Explaining the project

Okay, but what we built here?

The diagram below shows a little bit how the project is organized (which you can check on the Herbs doc home), the generated app is based on the clean architecture, there we have our domain with our entities and our usecases, around from this we have the rest of our application (api, database, clients, documentation, etc.) and in all these other layers use the herbsjs "glues" that read the entities and usecases metadatas to facilitate our day-a-day software development, remembering that no glue is a code generator, are just tools to speed up the development of your code.

Herbs archtecture ecosystem

now let's be a little practical and talk about the our project's folder structure.

NOTE: remembering that architecture is not a folder structure!

Folder structure

Inside domain folder we have our entities and usecases that provide some metadata as usecase's name, request and output schema, entity fields, types and more.

Above we have herbs2gql glue example, the herbs2gql receive a usecase and a resolver so it return the Graphql schemas string.

Glue example

The concept of “glue” runs throughout the application, from the presentation layer to the repository, remembering it isn't mandatory and this libraries are only meant to help you develop and maintain your project. :)

Inside our usecases we can see that exists not anly a simples creation, but there have a validations too, so it means the generated project is done to production!

Running the project

Once we've generated and understood the code, let's put this code to work! Just give the npm run start command and watch the magic happen. Note that all REST routes are printed on the terminal and that they are all based on our usecases.

REST terminal routes

Also, if you open your browser on the http://localhost:3000/graphql route you will see all generated schemas for the Graphql presentation layer with the Graphql playground documentation

graphql playground

Beside everything we generated here, the herbs generates a dynamic documentation too, so imagine you'll never need to worry about your API's documentation since everything will be automatically generated via usecase metadata and your Readme.md file! To do that we use the HerbsShelf glue

Herbsshlef

So now just implement your business rule and go up to production!

The end gif

Top comments (0)