blog > accelerating-full-stack-application-development-with-ai-tools-recap-of-our-devconf-workshop

Accelerating Full Stack Application Development with AI Tools: Recap of Our DevConf Workshop

by Yashlin Naidoo
Published on: 5/31/2023

Introduction:

Recently, we had the privilege of hosting a workshop at DevConf, where we explored the power of AI tools in accelerating full stack application development. We selected a stack comprising PostgreSQL in a Docker container, Node.js Express, and Angular to build a practical application—an intuitive vehicle finance calculator. Throughout the workshop, we showcased the remarkable capabilities of AI tools such as ChatGPT, AI Commits, Tabnine, and MIntlify. In this blog post, we will provide a comprehensive recap of the workshop, highlighting the potential of these AI tools in streamlining and enhancing the development process.

Introducing our AI Toolset

The Development Journey:

Throughout the workshop, we demonstrated how these AI tools seamlessly integrated into our development workflow. Starting from creating the Docker container and setting up the database, we progressed to developing the backend logic with Node.js Express, and finally, building a sleek and responsive frontend using Angular. At each step, we showcased the power of AI tools in simplifying tasks, increasing efficiency, and fostering developer creativity. Using this blog , you now also have the opportunity to follow along.

Pre-requisites:

Before we dive into the upcoming tasks, let’s ensure that you have all the necessary technologies installed on your laptop. Here’s a list of what you’ll need:

Repository Installation and Instructions

https://github.com/Runninghill/DevConf.Monorepo
To facilitate your understanding and follow along with the blog post, we have organized the repository into two branches: the Main branch and the Template branch.

  1. Main Branch: The Finished Application The Main branch of this repository contains the fully developed and polished version of the vehicle finance calculator application. If you’re interested in exploring the final product or examining the complete codebase, you can access it directly from the Main branch. This branch represents the culmination of our workshop and showcases the full potential of AI tools in accelerating full stack application development.
  2. Template Branch: Follow the Blog Post If you wish to dive into the workshop step-by-step and follow the guidance provided in the corresponding blog post, we recommend checking out the Template branch. This branch serves as a starting point, providing you with a template that aligns with the initial stage of the application development process as described in the blog post. By checking out the Template branch, you’ll have access to the codebase in its initial state, allowing you to follow the blog post instructions accurately.

Whether you choose to explore the Main branch to view the finished application or check out the Template branch to follow the blog post, this repository caters to your individual learning preferences. We encourage you to make the most of both branches to gain a comprehensive understanding of the workshop and the concepts discussed in the blog post.

Happy coding and learning!

Getting started

To get started, clone the repo , checkout the template branch , navigate to the README and start with the installation instructions.

Creating the Backend

Once you have cloned the repo the first step in building our vehicle finance calculator is to create the backend. We will use Node.js and PostgreSQL to create a simple API that will handle the calculations.

The first prompt

Navigate to the PROMPTS.md file and copy and paste the first prompt in ChatGPT

Your implementation might look something like this

Taking this code , we can then populate our init.sql file in the root directory of our repository

This concludes the database implementation , if your output from ChatGPT looks slightly different , fiddle around with your prompts until you get a similar implementation. If you’re sufficiently skilled in these technologies you can go with a slightly different implementation but if you’re a beginner , its best to try and follow along exactly.

Implementing the routes and CRUD operation

Now that we have the database set up lets continue in the same chat window in order to set up our express routes and database operations

Taking the next prompt

## Api

1. Generate routes file

```

Given that we have a nodejs express api, please generate a routes file called "vehicle-quote-routes.js" that we will put inside a folder called "routes" in our application. The api methods that should be generated is a "post" method for inserting a record and a "get" method to retrieve all the records in the table. Please also include the implementation for the pg database operations. Please also include a snippet of how to add that to the main application.

```

we might end up with something like this in our chat window

const express = require('express');
const router = express.Router();
const { Pool } = require('pg');

// Create a PostgreSQL connection pool
const pool = new Pool({
  connectionString: 'postgres://username:password@localhost:5432/database_name',
});

// POST method to insert a record into the vehiclequote table
router.post('/vehiclequote', async (req, res) => {
  try {
    const {
      vehicledescription,
      vehiclefinanceamount,
      interestrate,
      paymenttermmonths,
      monthlypaymentamount,
      totalpaymentamount,
    } = req.body;

    const client = await pool.connect();
    const query = `INSERT INTO vehiclequote (vehicledescription, vehiclefinanceamount, interestrate, paymenttermmonths, monthlypaymentamount, totalpaymentamount) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *`;
    const values = [
      vehicledescription,
      vehiclefinanceamount,
      interestrate,
      paymenttermmonths,
      monthlypaymentamount,
      totalpaymentamount,
    ];

    const result = await client.query(query, values);
    client.release();

    res.status(201).json(result.rows[0]);
  } catch (error) {
    res.status(500).json({ error: 'An error occurred while inserting the record.' });
  }
});

// GET method to retrieve all records from the vehiclequote table
router.get('/vehiclequote', async (req, res) => {
  try {
    const client = await pool.connect();
    const query = 'SELECT * FROM vehiclequote';
    const result = await client.query(query);
    client.release();

    res.status(200).json(result.rows);
  } catch (error) {
    res.status(500).json({ error: 'An error occurred while retrieving the records.' });
  }
});

module.exports = router;
const express = require('express');
const vehicleQuoteRoutes = require('./routes/vehicle-quote-routes');

const app = express();

// ...

app.use(express.json());

// Add the vehicle quote routes
app.use('/api', vehicleQuoteRoutes);

// ...

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

ChatGPT has not only developed the routes and database implementation but has also provided a comprehensive guide on how to utilize them effectively. It is crucial to carefully observe and follow the descriptions and instructions provided to ensure the correct implementation of your project.

  1. In your vehicleFinanceCalculatorApi root folder, create a new folder named “routes.”
  2. Inside the “routes” folder, create a new file named “vehicle-quote-routes.js.”

Your file and folder structure will look like this

Once you’ve done that , you can populate your file with your generated code and then make use of it in your index.js file. The next prompt will update your current implementation to perform the calculations within the post method.

2. Update routes file with calculations

```

please update the post method to automatically calculate the values for "monthlypaymentamount" and "totalpaymentamount"

```

Finalizing the back-end implementation

The 3rd and 4th prompts are a little tricky but the purpose of them is to automatically create swagger documentation for you to test your routes and document your API’s

3. Generate openapi doc

```

Please generate an openapi jsdoc for the two methods as well as the object schema

```

4. Generate swagger code

```

Please provide a snippet on how to serve a swagger in our application

```

Using those prompts will generate slightly different outputs but your final implementation should look similar to the following:

vehicle-quote-routes.js

const express = require('express');
const router = express.Router();
const { Pool } = require('pg');

/**
 * @openapi
 * components:
 *   schemas:
 *     VehicleQuote:
 *       type: object
 *       properties:
 *         id:
 *           type: integer
 *         vehicledescription:
 *           type: string
 *         vehiclefinanceamount:
 *           type: number
 *           format: float
 *         interestrate:
 *           type: number
 *           format: float
 *         paymenttermmonths:
 *           type: integer
 *         monthlypaymentamount:
 *           type: number
 *           format: float
 *         totalpaymentamount:
 *           type: number
 *           format: float
 *
 * /vehicle-quote:
 *   post:
 *     summary: Insert a new vehicle quote record
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/VehicleQuote'
 *     responses:
 *       '201':
 *         description: Successfully inserted the vehicle quote record
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/VehicleQuote'
 *       '500':
 *         description: An error occurred while inserting the vehicle quote
 *
 *   get:
 *     summary: Retrieve all vehicle quote records
 *     responses:
 *       '200':
 *         description: Successfully retrieved all vehicle quote records
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 $ref: '#/components/schemas/VehicleQuote'
 *       '500':
 *         description: An error occurred while retrieving vehicle quotes
 */

// Create a new Pool instance with your PostgreSQL connection details
const pool = new Pool({
  user: 'postgres',
  host: 'localhost',
  database: 'vehicle_finance_db',
  password: 'password',
  port: 5432, // Replace with your PostgreSQL port if necessary
});

// POST method to insert a record into the "vehiclequote" table
/* This is a POST method that inserts a new record into the "vehiclequote" table in a PostgreSQL
database. It takes in the request body which contains the vehicle description, vehicle finance
amount, interest rate, and payment term months. It then calculates the monthly payment amount and
total payment amount based on the input values. It uses a Pool instance to connect to the PostgreSQL
database and executes an SQL query to insert the new record with the calculated values. If the
insertion is successful, it returns the inserted record with a status code of 201. If there is an
error, it returns a status code of 500 with an error message. */
router.post('/', async (req, res) => {
    try {
      const {
        vehicledescription,
        vehiclefinanceamount,
        interestrate,
        paymenttermmonths,
      } = req.body;
  
      // Calculate monthly payment amount
      const monthlyinterestrate = interestrate / 100 / 12;
      const totalinterestrate = Math.pow(1 + monthlyinterestrate, paymenttermmonths);
      const monthlypaymentamount = (vehiclefinanceamount * monthlyinterestrate * totalinterestrate) / (totalinterestrate - 1);
  
      // Calculate total payment amount
      const totalpaymentamount = monthlypaymentamount * paymenttermmonths;
  
      const client = await pool.connect();
      const query =
        'INSERT INTO vehiclequote (vehicledescription, vehiclefinanceamount, interestrate, paymenttermmonths, monthlypaymentamount, totalpaymentamount) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *';
      const values = [
        vehicledescription,
        vehiclefinanceamount,
        interestrate,
        paymenttermmonths,
        monthlypaymentamount.toFixed(2),
        totalpaymentamount.toFixed(2),
      ];
  
      const result = await client.query(query, values);
      const insertedQuote = result.rows[0];
      client.release();
  
      res.status(201).json(insertedQuote);
    } catch (error) {
      console.error('Error inserting vehicle quote:', error);
      res.status(500).json({ error: 'An error occurred while inserting the vehicle quote.' });
    }
  });
  

// GET method to retrieve all records from the "vehiclequote" table
router.get('/', async (req, res) => {
  try {
    const client = await pool.connect();
    const query = 'SELECT * FROM vehiclequote';
    const result = await client.query(query);
    const quotes = result.rows;
    client.release();

    res.json(quotes);
  } catch (error) {
    console.error('Error retrieving vehicle quotes:', error);
    res.status(500).json({ error: 'An error occurred while retrieving vehicle quotes.' });
  }
});

module.exports = router;

index.js

const express = require('express');
const cors = require("cors");
const app = express();
const port = 3000;
app.use(express.json())
app.use(cors());

const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');

app.get('/', (req, res) => {
res.send({message:'Hello, Devconf!'});
});

// routes here
const vehicleQuoteRoutes = require('./routes/vehicle-quote-routes');

app.use('/vehicle-quote', vehicleQuoteRoutes);

const swaggerOptions = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'Vehicle Quote API',
      version: '1.0.0',
    },
  },
  apis: ['routes/vehicle-quote-routes.js'], // Specify the routes file(s) that contain the OpenAPI JSDoc
};

const swaggerSpec = swaggerJsdoc(swaggerOptions);

// Serve Swagger UI
app.use('/api-docs', swaggerUi.serve);
app.get('/api-docs', swaggerUi.setup(swaggerSpec));

app.listen(port, () => {
  console.log(`Example app listening at <http://localhost>:${port}`);
});

and lastly , the package.json which keeps track of your dependencies

{
  "name": "vehiclefinancecalculatorapi",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \\"Error: no test specified\\" && exit 1",
    "start": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "pg": "^8.11.0",
    "swagger-jsdoc": "^6.2.8",
    "swagger-ui-express": "^4.6.3"
  }
}

Important:

before you test , please make sure you install all the new dependencies on your project. Check that your package.json looks similar to the one above and run npm install on your terminal in the root of the vehicleFinanceCalculatorApi folder

That concludes the back-end implementation , you can run the NodeJS application and database by referring to the instructions in the README and then use the swagger at http://localhost:3000/api-docs/ to test

Finalizing the front-end implementation

Once we have our backend set up, we can move on to creating the frontend. We will use Angular to create a single-page application that will allow users to input the vehicle finance information and see the results.

Implementing the form group and interfaces

Running the angular application will give you a beautiful , semi-completed user interface

This will form the base for our front-end implementation. We can navigate to the PROMPTS file to start with our front-end implementation.

1. Generate typescript interface

```

Given the following DB script , please create an angular typescript interface

CREATE TABLE vehiclequote (

  vehicledescription VARCHAR(255),

  vehiclefinanceamount NUMERIC(10, 2),

  interestrate NUMERIC(5, 2),

  paymenttermmonths INTEGER

);

```

2. Generate formgroup

```

using the interface generated above, create an angular formgroup function

```

Once again , using the prompts we would get the following output

The second prompt will allow us use that interface to create our formgroup implementation

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { VehicleQuote } from './vehicle-quote.interface';

@Component({
  selector: 'app-vehicle-quote-form',
  templateUrl: './vehicle-quote-form.component.html',
  styleUrls: ['./vehicle-quote-form.component.css']
})
export class VehicleQuoteFormComponent implements OnInit {
  vehicleQuoteForm: FormGroup;

  constructor(private formBuilder: FormBuilder) { }

  ngOnInit(): void {
    this.buildForm();
  }

  buildForm(): void {
    this.vehicleQuoteForm = this.formBuilder.group({
      vehicledescription: ['', Validators.required],
      vehiclefinanceamount: ['', [Validators.required, Validators.pattern(/^\\d+(\\.\\d{1,2})?$/)]],
      interestrate: ['', [Validators.required, Validators.pattern(/^\\d+(\\.\\d{1,2})?$/)]],
      paymenttermmonths: ['', Validators.required]
    });
  }

  submitForm(): void {
    if (this.vehicleQuoteForm.valid) {
      const vehicleQuote: VehicleQuote = this.vehicleQuoteForm.value;
      // Do something with the vehicleQuote object
      console.log(vehicleQuote);
    } else {
      // Handle form validation errors
    }
  }
}

Here we need to be careful not to copy and paste everything , just the items we need which in this case will be the buildform() method , the submitForm() method and all of the necessary declarations and imports

Implementing the service calls and binding to the html

The next few prompts help us connect our front-end to our database using rxjs , there is a manual implementation required for binding our methods and objects to our html. There is also manual intervention for a second interface that is required. you can use Tabnine to help you write the code here. Tabnine can help you write code faster and with fewer errors by providing suggestions for code completions, function arguments, and variable names.

3. Generate insert method to api

```

using this implementation as an example

public getInfo() {

    this.http.get('http://localhost:3000/').subscribe(element => {

        this.element = element;

    }, error=> {

        console.log(error);

    });

}

create a similar implementation to call this route router.post('/vehiclequote', async (req, res)

```

4. Generate get method to api

```

create another similar implementation to call this route

router.get('/vehiclequote`, async (req, res) => {

```

We’re expecting our app.component.ts and app.component.html to look like this when we’re done

app.component.ts

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  element: any ;
  title = 'vehicleFinanceCalculatorUi';
  vehicleQuoteForm: any;
  vehicleQuotes: GetVehicleQuote[]= []
  constructor(private http:HttpClient , private formBuilder: FormBuilder){
    this.getInfo()
    this.vehicleQuoteForm = this.formBuilder.group({
      vehicledescription: ['', Validators.required],
      vehiclefinanceamount: ['', Validators.required],
      interestrate: ['', Validators.required],
      paymenttermmonths: ['', Validators.required],
    });
    this.getVehicleQuotes();
  }
  public getInfo(){
    this.http.get('<http://localhost:3000/>').subscribe(element =>{
      this.element = element;
    },
    error=>{
      console.log(error);

    });
  }

  sendVehicleQuote(vehicleQuote:InsertVehicleQuote) {
    const url = '<http://localhost:3000/vehicle-quote>';
    this.http.post(url, vehicleQuote)
      .subscribe(response => {
        this.getVehicleQuotes();
        // Handle the response here
        console.log(response);
      }, error => {
        // Handle the error here
        console.log(error);
      });
  }

  getVehicleQuotes() {
    const url = '<http://localhost:3000/vehicle-quote>';

    this.http.get<GetVehicleQuote[]>(url)
      .subscribe(vehicleQuotes => {
        // Handle the response here
        this.vehicleQuotes = vehicleQuotes;
      }, error => {
        // Handle the error here
        console.log(error);
      });
  }

  formatLabelPercentage(value: number): string {
    return `${value}%`;
  }

  formatLabelMonths(value: number): string {
    return `${value}`;
  }

  submitForm() {
    if (this.vehicleQuoteForm.valid) {
      const vehicleQuote: InsertVehicleQuote = this.vehicleQuoteForm.value;
      // Process the submitted vehicle quote data
      console.log(vehicleQuote);
      this.sendVehicleQuote(vehicleQuote)
    } else {
      // Handle form validation errors
    }
  }

}

export interface GetVehicleQuote {
  id: number;
  vehicledescription: string;
  vehiclefinanceamount: number;
  interestrate: number;
  paymenttermmonths: number;
  monthlypaymentamount: number;
  totalpaymentamount: number;
}

export interface InsertVehicleQuote {
  vehicledescription: string;
  vehiclefinanceamount: number;
  interestrate: number;
  paymenttermmonths: number;
}

app.component.html

<body translate="no">

  <div class="container">
  <div class="sky">

    <div class="stars"></div>
    <div class="stars1"></div>

    <div class="stars2"></div>
    <div class="shooting-stars"></div>

    <div fxLayout="row" fxLayoutAlign="center center"  >
      <div fxFlex.gt-lg="20" fxFlex.gt-md="20" fxFlex.gt-xs="20" fxFlex="20">
        <img src="../assets/RUNNINGHILL LOGO_TRANSPARENT_EXCL_ICECREAM_SCOOP.png" height="70%" width="70%" alt="rhlogo"/>
      </div>

      <!-- column -->
      <div fxFlex.gt-lg="60" fxFlex.gt-md="60" fxFlex.gt-xs="60" fxFlex="60">
        <h1 class="sign">HELLO DEVCONF</h1>
      </div>

      <div fxFlex.gt-lg="20" fxFlex.gt-md="20" fxFlex.gt-xs="20" fxFlex="20">

      </div>

    </div>
    <div fxLayout="row"  fxLayoutGap="1%" fxLayoutAlign="center center">
      <!-- column -->
      <div fxFlex.gt-lg="50" fxFlex.gt-md="50" fxFlex.gt-xs="50" fxFlex="50">
        <mat-card style="border: 3px solid  #03A9F4">

          <form [formGroup]="vehicleQuoteForm">
            <div fxLayout="row wrap" class="padding-2">
              <!-- column -->
              <div fxFlex.gt-lg="100" fxFlex.gt-md="100" fxFlex.gt-xs="100" fxFlex="100">
                <p>
                  <mat-form-field appearance="outline" class="width-100">
                    <mat-label>Vehicle Description</mat-label>
                    <input matInput placeholder="Placeholder" formControlName="vehicledescription" />
                    <mat-icon matSuffix>directions_car</mat-icon>
                  </mat-form-field>
                </p>
              </div>
              <div fxFlex.gt-lg="100" fxFlex.gt-md="100" fxFlex.gt-xs="100" fxFlex="100">
                <p>
                  <mat-form-field appearance="outline" class="width-100">
                    <mat-label>Vehicle Finance Amount</mat-label>
                    <input matInput type="number" placeholder="Placeholder" formControlName="vehiclefinanceamount" />
                    <mat-icon matSuffix>account_balance</mat-icon>
                  </mat-form-field>
                </p>
              </div>
              <div fxFlex.gt-lg="100" fxFlex.gt-md="100" fxFlex.gt-xs="100" fxFlex="100">
                <h3>Payment term (months)</h3>
                <mat-slider
                  min="0"
                  max="120"
                  step="12"
                  discrete
                  [displayWith]="formatLabelMonths"
                  class="width-100"

                >
                  <input matSliderThumb formControlName="paymenttermmonths" />
                </mat-slider>
              </div>
              <div fxFlex.gt-lg="100" fxFlex.gt-md="100" fxFlex.gt-xs="100" fxFlex="100">
                <h3>Interest Rate</h3>
                <mat-slider
                  min="0"
                  max="30"
                  step="0.1"
                  discrete
                  [displayWith]="formatLabelPercentage"
                  class="width-100"

                >
                  <input matSliderThumb  formControlName="interestrate" />
                </mat-slider>
              </div>
            </div>
            <mat-card-actions>
              <button mat-button color="primary" type="submit" (click)="submitForm()">Save and Calculate</button>
            </mat-card-actions>
          </form>

        </mat-card>
      </div>
    </div>

    <div fxLayout="row" class="padding-2" fxLayoutGap="1%" fxLayoutAlign="center center">

      <div fxFlex.gt-lg="25" fxFlex.gt-md="25" fxFlex.gt-xs="25" fxFlex="25" *ngFor="let item of vehicleQuotes">
        <mat-card style="border: 3px solid  #03A9F4">
          <div fxLayout="row" class="padding-2" fxLayoutGap="1%">
            <!-- column -->
            <div
              fxFlex.gt-lg="100"
              fxFlex.gt-md="100"
              fxFlex.gt-xs="100"
              fxFlex="100"
            >
            <h3>Vehicle Description{{item.vehicledescription}}</h3>
            <h3>Vehicle Finance Amount{{item.vehiclefinanceamount}}</h3>
            <h3>Payment Term {{item.paymenttermmonths}}</h3>
            <h3>Interest Rate {{item.interestrate}}</h3>
            <h3>Total Monthly Payment {{item.monthlypaymentamount}}</h3>
            <h2>Total Payment {{item.totalpaymentamount}}</h2>
          </div>
          </div>
        </mat-card>
      </div>
    </div>

  </div>
</div>

Testing our vehicle finance calculator

Now that we’ve gone through that wonderful implementation , we can start creating vehicle finance configurations. This application helps us create and compare different vehicle finance options , helping us make a more informed purchase decision

If you struggled to follow or the outputs generated by ChatGpt were completely different , you can always refer to the main branch for the completed application.

The supporting actors

Mintlify

The last step is to document our code. We can do this easily using the mintlify vscode extension

By highlighting the code we wish to document , we can click on generate docs in the mintlify extension window

AI Commits

Now that we’ve implemented all this logic , we can use the ai commits npm package to commit everything. Make sure you stage your commits into bite sized commits otherwise you will hit the token limit of OpenAI

Conclusion

In this tutorial, we have explored how you can use ChatGPT, Tabnine, AICommits, and Mintlify, along with PostgreSQL, Node.js, Angular, and Docker, to create a vehicle finance calculator. These tools can help you save time, reduce errors, and focus on building the best possible application.

So go ahead and start building your full-stack application today!

WRITTEN BY

Yashlin Naidoo