You are currently viewing How I built MapleStack’s waitlist with AI: ChatGPT vs Bard

How I built MapleStack’s waitlist with AI: ChatGPT vs Bard

Recently, I publicly shared a project that I’m working on, MapleStack, a platform for connecting companies everywhere to tech talent in Canada.

Today, I’ll like to share how I built MapleStack’s waitlist form in minutes.

The Problem

I need to create a simple form that collects user information, stores it somewhere and notifies the user via email.

The Solution

Although it is a simple problem to reason about, I like the fact that it touches on major features that make up a web application. To get this working we need to do the following:

  • Frontend: Create a user form, input validation, error handling on the client
  • Backend: Input validation, Data storage, error handling
  • Notification: Email delivery service

Additionally, we still need to deploy the app, configure the endpoint, DNS records, configure TLS certificates, and probably a few other things.

We can already see how a “simple” task suddenly contains a lot of moving parts.

My options? Pay for a Service or build.

You are here because I chose the latter.

To get this done quickly, I decided to design this using familiar tools

  • Frontend:  React form component
  • Backend: AWS API Gateway + AWS Lambda
  • Storage: AWS DynamoDB
  • Notification: AWS SES

Leveraging AWS products helps me get setup quickly. I don’t need to worry about configuring application servers, email servers and more. Now, I just need to write the code for the frontend form, processing the user input and sending off the notification.

Coding with  AI: ChatGPT vs Bard

I missed the major hype cycle and only started using ChatGPT a few weeks back. I’ve heard the litany of things that it can get done so I decided to give it a test.

With no knowledge of prompts, I decided to just talk to it as if I was thinking about the task that needs to be done and it sort of worked. After a few iterations, here are the final prompts.

The Prompt – React Form

Write a React component that is used to collect user information to join a waitlist. It should satisfy the following criteria

1. Code is in Typescript.

2. Collect these details: Name, Email, Usage with two options candidate or employer

3. All inputs are required, Show error messages if when errors occur.

4. Use react-hook-form to manage the form

5. Use @tanstack/react-query to submit the details to an endpoint(TBD)

6. Use Tailwind CSS for styling

That’s all I can think of for now. Do you have any questions or suggestions to add?

Response – ChatGPT

import React from 'react';
import { useForm } from 'react-hook-form';
import { useMutation } from '@tanstack/react-query';

interface WaitlistFormData {
  name: string;
  email: string;
  usage: 'candidate' | 'employer';
}

const WaitlistForm: React.FC = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<WaitlistFormData>();

  const mutation = useMutation((data: WaitlistFormData) => {
    // Call your API endpoint with the form data
    console.log(data);
    // Replace the console.log with the actual API call
    // For example: axios.post('/api/waitlist', data);
  });

  const onSubmit = handleSubmit((data) => {
    mutation.mutate(data);
  });

  return (
    <form onSubmit={onSubmit} className="max-w-md mx-auto">
      <div className="mb-4">
        <label htmlFor="name" className="block font-medium text-gray-700">
          Name
        </label>
        <input
          type="text"
          id="name"
          {...register('name', { required: 'Name is required' })}
          className={`form-input mt-1 block w-full ${
            errors.name ? 'border-red-500' : 'border-gray-300'
          }`}
        />
        {errors.name && (
          <p className="text-red-500 text-sm mt-1">{errors.name.message}</p>
        )}
      </div>

      <div className="mb-4">
        <label htmlFor="email" className="block font-medium text-gray-700">
          Email
        </label>
        <input
          type="email"
          id="email"
          {...register('email', {
            required: 'Email is required',
            pattern: {
              value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
              message: 'Invalid email address',
            },
          })}
          className={`form-input mt-1 block w-full ${
            errors.email ? 'border-red-500' : 'border-gray-300'
          }`}
        />
        {errors.email && (
          <p className="text-red-500 text-sm mt-1">{errors.email.message}</p>
        )}
      </div>

      <div className="mb-4">
        <label htmlFor="usage" className="block font-medium text-gray-700">
          Usage
        </label>
        <select
          id="usage"
          {...register('usage', { required: 'Usage is required' })}
          className={`form-select mt-1 block w-full ${
            errors.usage ? 'border-red-500' : 'border-gray-300'
          }`}
        >
          <option value="">Select usage</option>
          <option value="candidate">Candidate</option>
          <option value="employer">Employer</option>
        </select>
        {errors.usage && (
          <p className="text-red-500 text-sm mt-1">{errors.usage.message}</p>
        )}
      </div>

      <button
        type="submit"
        className="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded"
      >
        Join Waitlist
      </button>
    </form>
  );
};

export default WaitlistForm;
Code language: JavaScript (javascript)

Response – Bard

import { useQuery } from '@tanstack/react-query';
import React, { useEffect, useState } from 'react';
import { Field, Form, Formik } from 'react-hook-form';
import { tailwind } from 'tailwindcss';

const WaitlistForm = () => {
    const [name, setName] = useState('');
    const [email, setEmail] = useState('');
    const [usage, setUsage] = useState('');

    const query = useQuery('waitlist', async () => {
        return await fetch('/api/waitlist', {
            method: 'POST',
            body: JSON.stringify({
                name,
                email,
                usage,
            }),
        });
    });

    useEffect(() => {
        if (query.isSuccess) {
            alert('Thank you for joining the waitlist!');
        }
    }, [query.isSuccess]);

    return (
        <Formik
            initialValues={{ name, email, usage }}
            onSubmit={async (values) => {
                await query.mutate(values);
            }}
        >
            <Form>
                <Field
                    name="name"
                    type="text"
                    label="Name"
                    placeholder="Your name"
                    required
                    error={name === '' ? 'Name is required' : undefined}
                />
                <Field
                    name="email"
                    type="email"
                    label="Email"
                    placeholder="Your email address"
                    required
                    error={email === '' ? 'Email is required' : undefined}
                />
                <Field
                    name="usage"
                    type="select"
                    label="Usage"
                    required
                    options={[
                        { value: 'candidate', label: 'Candidate' },
                        { value: 'employer', label: 'Employer' },
                    ]}
                />
                <button type="submit">Join Waitlist</button>
            </Form>
        </Formik>
    );
};

export default WaitlistForm;
Code language: TypeScript (typescript)

To be succinct, I was impressed! Although Bard generated some incorrect code, the majority of it was still usable. ChatGPT on the other hand seems to have provided a more complete output.

Armed with this code, I decided to do the same for the backend.

The Prompt – Node JS lambda function

Write an AWS Lambda NodeJS function that satisfies the following conditions:

1. It will be called by the API Gateway.

2. The function should save the data to a DynamoDB table.

3. The expected input is a JSON object with the following properties:

a. name

b. email

c. usage

4. It should validate the input. The name and email should be required. The email should be a valid email address. The usage should be one of the following values: candidate, employer.

5. Using SES, it should send a confirmation email to the user, thanking them for joining the waitlist. The email should contain the following information:

a. Name

b. Email

c. Usage

6. The function should return a JSON object with the following properties:

a. success

b. message

7. The function should return a 400 error if the input is invalid.

8. The function should return a 500 error if there is an internal error.

9. The function should return a 200 if the data was saved successfully.

10. It should handle OPTIONS http method.

11. It should handle POST http method.

12. DynamoDB table name should be passed as an environment variable.

13. SES email address should be passed as an environment variable

That’s all I can think of for now. Do you have any questions or suggestions to add?

Response – ChatGPT

const AWS = require('aws-sdk');

const dynamodb = new AWS.DynamoDB.DocumentClient();
const ses = new AWS.SES();

const dynamoDBTableName = process.env.DYNAMODB_TABLE_NAME;
const sesEmailAddress = process.env.SES_EMAIL_ADDRESS;

exports.handler = async (event) => {
  try {
    // Handle OPTIONS request for CORS preflight
    if (event.httpMethod === 'OPTIONS') {
      return createResponse(200, { success: true, message: 'Preflight request handled successfully' });
    }

    // Handle POST request
    if (event.httpMethod === 'POST') {
      const { name, email, usage } = JSON.parse(event.body);

      // Validate the input
      if (!name || !email || !usage || !validateEmail(email) || !validateUsage(usage)) {
        return createResponse(400, { success: false, message: 'Invalid input' });
      }

      // Save the data to DynamoDB
      await saveDataToDynamoDB(name, email, usage);

      // Send confirmation email using SES
      await sendConfirmationEmail(name, email, usage);

      return createResponse(200, { success: true, message: 'Data saved and email sent successfully' });
    }

    // Handle unsupported HTTP methods
    return createResponse(405, { success: false, message: 'Method Not Allowed' });
  } catch (error) {
    console.error('Error:', error);
    return createResponse(500, { success: false, message: 'Internal server error' });
  }
};

const validateEmail = (email) => {
  // Basic email validation using regular expression
  const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
  return emailRegex.test(email);
};

const validateUsage = (usage) => {
  // Check if usage is one of the allowed values
  const allowedUsageValues = ['candidate', 'employer'];
  return allowedUsageValues.includes(usage);
};

const saveDataToDynamoDB = async (name, email, usage) => {
  const params = {
    TableName: dynamoDBTableName,
    Item: { name, email, usage },
  };

  await dynamodb.put(params).promise();
};

const sendConfirmationEmail = async (name, email, usage) => {
  const subject = 'Thank you for joining the waitlist';
  const message = `Thank you for joining the waitlist!\n\nName: ${name}\nEmail: ${email}\nUsage: ${usage}`;

  const params = {
    Destination: { ToAddresses: [email] },
    Message: {
      Body: { Text: { Data: message } },
      Subject: { Data: subject },
    },
    Source: sesEmailAddress,
  };

  await ses.sendEmail(params).promise();
};

const createResponse = (statusCode, body) => {
  return {
    statusCode,
    body: JSON.stringify(body),
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Credentials': true,
    },
  };
};
Code language: JavaScript (javascript)

Response – Bard

const AWS = require("aws-sdk");
const DynamoDB = new AWS.DynamoDB();
const SES = new AWS.SES();

exports.handler = async (event, context) => {
  const { name, email, usage } = JSON.parse(event.body);

  // Validate the input data
  if (!name || !email || !usage) {
    return {
      success: false,
      message: "Invalid input data",
    };
  }

  // Check if usage is valid
  const validUsages = ["candidate", "employer"];
  if (!validUsages.includes(usage)) {
    return {
      success: false,
      message: "Invalid usage",
    };
  }

  // Save the data to DynamoDB
  const params = {
    TableName: process.env.DYNAMODB_TABLE_NAME,
    Item: {
      Name: name,
      Email: email,
      Usage: usage,
    },
  };
  await DynamoDB.putItem(params);

  // Send a confirmation email
  const params = {
    Destination: {
      ToAddresses: [email],
    },
    Message: {
      Subject: "Thank you for joining the waitlist",
      Body: {
        Text: {
          Data: `Thank you for joining the waitlist. We will notify you when we are ready to launch.

Name: ${name}
Email: ${email}
Usage: ${usage}`,
        },
      },
    },
  };
  await SES.sendEmail(params);

  // Return a success message
  return {
    success: true,
    message: "Data saved successfully",
  };
};

// This function is used to handle the OPTIONS http method.
exports.options = async (event, context) => {
  return {
    statusCode: 200,
  };
};

// This function is used to handle the POST http method.
exports.post = async (event, context) => {
  return exports.handler(event, context);
};
Code language: JavaScript (javascript)

Impressed again!

Thoughts

  • For this task, ChatGPT seemed to produce better code structure than Bard.
  • ChatGPT asked clarifying questions. This probably helped it generate more relevant code.
  • Bard didn’t ask clarifying questions and just produced an output it felt was appropriate.
  • The code provided was good enough and only need a few edits from me. Mainly extra checks and validation.
  • It reduced development time to minutes!
  • It’s not bulletproof. The code still had errors, the most common was related to using the function in a deprecated or wrong way. 

I know there has been a lot of talk about AI and replacing programmers and after using it, I think it’s more of an assistant. Will it make us redundant? Maybe, some day.. but for now it’s helping me get things done faster and that’s amazing!

If you have not already done so, check out MapleStack and join the waitlist that was built using the code above.

Also, follow MapleStack’s Twitter and LinkedIn pages for updates.

As always, feel free to reach out if you have any questions or just for a chat 🙂

Leave a Reply