Building a serverless REST API on AWS with ChatGPT 3.5/4

March 15, 2023

This article was originally written on March 15th, the day after ChatGPT 4 was released. The first part of the article covers my first interaction with ChatGPT 3.5 and then moves into ChatGPT 4 which was written and tested on the night of March 15th by purchasing ChatGPT Plus. I hope you enjoy!

We have all heard the whispers of something called ChatGPT which is destined to augment more trivial tasks we do as developers or humans in general to accelerate innovation. The story goes by leveraging ChatGPT we will be able to throw non differentiating work away in favor of AI based automation, freeing us up to spend closer to 100% on the highest impact work. Personally, I firmly believe that just like the inception of the internet, tools like ChatGPT, and it is a tool, will simply allow us to do more than ever before while at the same time breaking down barriers of entry for all humans globally.

From my optimistic perspective, it will lead to job creation and job loss the same way the internet did. It’s scary, it’s a new frontier, the future is not certain, but we are also witnessing a massive shift that will define the next 100+ years of human development.

Now to the topic, building a serverless rest api on aws with ChatGPT.

In this article we will cover:

  • Three different prompts that grow in complexity as we see the results given back by ChatGPT
  • Breakdown each section of the responses to shine light on what everything means
  • Conclude with a rating of effectiveness from 1/10 based on my experience as a Serverless Developer
  • BONUS: ChatGPT 4 prompts have been added in (it’s wild) keep reading past ChatGPT 3.5 to see how we take the basic IaC and turn into advanced IaC

Personally, I’ve been building serverless rest apis on aws with Serverless framework for more than 5 years. I’ve built a consulting company, serverlessguru.com around doing exactly this. The fear I hold with ChatGPT is how the introduction of AI could potentially replace part of our work, but that’s why I’m choosing to write on this topic. I want to know how easy it is to do, what caveats we hit, and where I can learn to improve or augment how I think about serverless development in 2023 to account for this new technological breakthrough.

For context, I’ve worked with ChatGPT already to help brainstorm some development ideas I had around sending templated emails from slack using / commands. However, while ChatGPT was helpful it was also just straight up making things up in a very confident way. For example, it gave me step by step instructions on how to setup the email sending from slack with templated emails including referencing platform steps for SendGrid and Twilio that simply didn’t exist. This had the opposite effect, instead of speeding me up, it kind of made me spin in a circle. So with that context let’s dive in.

The architecture we are going to build:

  1. AWS Lambda - compute for our simple application code
  2. AWS API Gateway - how we setup our routes, HTTP methods, JWT auth connected to AWS Lambda
  3. AWS Cognito User Pool - authentication pool for JWT based auth
  4. Amazon DynamoDB - our simple nosql database

Let’s start asking ChatGPT to build us some stuff.

Prompt #1

“I want to build a serverless rest api on AWS using serverless framework. I need to create the following resources AWS Lambda, AWS API Gateway, AWS Cognito User Pool, and Amazon DynamoDB. I need AWS API Gateway to connect with AWS Lambda. Can you generate some Serverless framework YAML for me?”

Let’s go over the result in parts so I can explain a bit.

  
service: my-rest-api

plugins:
  - serverless-plugin-typescript
  - serverless-offline

provider:
  name: aws
  runtime: nodejs14.x
  stage: dev
  region: us-east-1
  memorySize: 256
  timeout: 10
  

The first section we are highlighting is the service, plugins, and provider block. The first critique I have is the fact it by default choose to use TypeScript, serverless-plugin-typescript, and also that it didn’t ask any clarifying questions, potentially this is addressed with ChatGPT 4.

The usage of serverless-offline is fairly common when building serverless rest apis, so that’s great.

The provider section, I’m seeing the usage of nodejs14.x vs the most up to date runtime, but that’s likely because ChatGPT 3 is using training data from 2021, so will give a pass on that one.

The usage of timeout 10 seconds is something that likely will trip our developer up later on as well as memory 256. With timeout specifically I typically will set it equal to 30 seconds which is the hard limit of API Gateway. With memory I will typically go for something a bit more powerful e.g. 1024 or 1536. All those these two won’t cause our basic setup to break, it also doesn’t completely set us up for success as we scale more than a demo.

  
functions:
  myFunction:
    handler: src/handlers/myFunction.handler
    events:
      - http:
          path: /my-path
          method: post
          cors: true
          authorizer:
            arn: ${self:custom.cognitoArn}
  # Add more functions as needed
  

In this section, ChatGPT has generated a single function for us which hits all of the points I would want. I wish it asked clarifying questions so I could give an even better result, but likely this is do to my prompt not being enough. At the end, we can update our prompt to ask for more then measure how it does.

  
resources:
  Resources:
    myTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: my-table
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
  

Looking at the Amazon DynamoDB table it generated, I have no issues, but I would prefer the default be PAY_PER_REQUEST vs Provisioned Throughput as we will now pick up a slight AWS bill. However, with Read/Write capacity of 1, it’s likely covered by free tier so that’s okay. Everything else looks good.

  
resources:
  Resources:
    myUserPool:
      Type: AWS::Cognito::UserPool
      Properties:
        UserPoolName: my-user-pool
        UsernameAttributes:
          - email
        AutoVerifiedAttributes:
          - email
        Policies:
          PasswordPolicy:
            MinimumLength: 8
            RequireNumbers: true
            RequireSymbols: true
            RequireUppercase: true
            RequireLowercase: true
    myUserPoolClient:
      Type: AWS::Cognito::UserPoolClient
      Properties:
        ClientName: my-user-pool-client
        GenerateSecret: false
  

Another area I’m noticing which the developer will have to pick up is creating a consitent naming convention, which leverages the service name and stage, to ensure that we don’t hit downstream errors when deploying via AWS CloudFormation. Instead of my-user-pool, I’d go for ${self:service}-${self:provider.stage}-my-user-pool. If anything it just means that ChatGPT needs to be fed in templates written by your team already, which apparently ChatGPT 4 supports, haven’t tested.

There ended up being an error with the ChatGPT network, which caused the full YAML snippet to be cut off, but I’ve regenerated. And the critique I now have is that it gave me a completely different result this time with the same prompt. The worst part it’s now using AWS SAM inside a serverless framework project. Let me show you.

  
service: my-service-name
provider:
  name: aws
  runtime: nodejs14.x
  region: us-east-1

plugins:
  - serverless-offline

resources:
  Resources:
    UsersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: users
        AttributeDefinitions:
          - AttributeName: userId
            AttributeType: S
        KeySchema:
          - AttributeName: userId
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1

    UserPool:
      Type: AWS::Cognito::UserPool
      Properties:
        UserPoolName: my-user-pool
        AutoVerifiedAttributes:
          - email
        Policies:
          PasswordPolicy:
            MinimumLength: 8
            RequireLowercase: true
            RequireNumbers: true
            RequireSymbols: false
            RequireUppercase: true
        Schema:
          - AttributeDataType: String
            Name: email
            Required: true

    UserPoolClient:
      Type: AWS::Cognito::UserPoolClient
      Properties:
        UserPoolId: !Ref UserPool
        ClientName: my-user-pool-client

    CreateUserFunction:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: .
        Handler: create-user.handler
        Runtime: nodejs14.x
        Timeout: 30
        Environment:
          USERS_TABLE: !Ref UsersTable
          USER_POOL_ID: !Ref UserPool
        Events:
          CreateUser:
            Type: Api
            Properties:
              Path: /users
              Method: post
              RestApiId: !Ref ApiGateway

    GetUsersFunction:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: .
        Handler: get-users.handler
        Runtime: nodejs14.x
        Timeout: 30
        Environment:
          USERS_TABLE: !Ref UsersTable
        Events:
          GetUsers:
            Type: Api
            Properties:
              Path: /users
              Method: get
              RestApiId: !Ref ApiGateway

    ApiGateway:
      Type: AWS::ApiGateway::RestApi
      Properties:
        Name: my-api-gateway

  Outputs:
    ApiGatewayRestApiId:
      Value: !Ref ApiGateway
      Export:
        Name: ApiGatewayRestApiId

    ApiGatewayRestApiRootResourceId:
      Value: !GetAtt ApiGateway.RootResourceId
      Export:
        Name: ApiGatewayRestApiRootResourceId

    UserPoolClientId:
      Value: !Ref UserPoolClient
      Export:
        Name: UserPoolClientId
  

To dive into what is AWS SAM and what isn’t let me clean up the code snippet above.

  
resources:
  Resources:
    CreateUserFunction:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: .
        Handler: create-user.handler
        Runtime: nodejs14.x
        Timeout: 30
        Environment:
          USERS_TABLE: !Ref UsersTable
          USER_POOL_ID: !Ref UserPool
        Events:
          CreateUser:
            Type: Api
            Properties:
              Path: /users
              Method: post
              RestApiId: !Ref ApiGateway

    GetUsersFunction:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: .
        Handler: get-users.handler
        Runtime: nodejs14.x
        Timeout: 30
        Environment:
          USERS_TABLE: !Ref UsersTable
        Events:
          GetUsers:
            Type: Api
            Properties:
              Path: /users
              Method: get
              RestApiId: !Ref ApiGateway

    ApiGateway:
      Type: AWS::ApiGateway::RestApi
      Properties:
        Name: my-api-gateway
  

Now this without any existing knowledge of building serverless applications via serverless framework would 100% lead the developer astray and absolutely cause a developer to go down the wrong path even if the deployment works. How I would modify to fit in a serverless framework way.

  
functions:
  createUserFunction:
    handler: ...
    events:
      - http:
          path: /create
          method: post
          cors: true
          authorizer:
            arn: ${self:custom.cognitoArn}
  
  getUsersFunction:
    handler: ...
    events:
      - http:
          path: /get
          method: get
          cors: true
          authorizer:
            arn: ${self:custom.cognitoArn}
  

Okay let’s look at the rest, that was generated.

  
resources:
  Outputs:
    ApiGatewayRestApiId:
      Value: !Ref ApiGateway
      Export:
        Name: ApiGatewayRestApiId

    ApiGatewayRestApiRootResourceId:
      Value: !GetAtt ApiGateway.RootResourceId
      Export:
        Name: ApiGatewayRestApiRootResourceId

    UserPoolClientId:
      Value: !Ref UserPoolClient
      Export:
        Name: UserPoolClientId
  

The Outputs section is what makes key values visible to other AWS Cloudformation stacks in the region where our stuff is deployed. However, what is not being done is stack splitting, which is basically isolating certain resources and then using Outputs to link them together.

How I would have split this:

  1. Stack A - AWS Lambda + API Gateway
  2. Stack B - AWS Cognito User Pool
  3. Stack C - Amazon DynamoDB

Each one would be deployed in a seperate serverless.yml file that leverages AWS CloudFormation inputs and outputs to stitch the stacks together while also creating boundaries between files so that each can be deployed independetly. Some people disagree with this approach, but I strongly disagree with them haha I’ll avoid ranting more on this one.

Finally ChatGPT in the second run after the ChatGPT network crashed, gave us this breakdown explanation of things. I don’t have any big issues with what it described is inside of this file, but obviously the mixing of AWS SAM into Serverless Framework is no bueno.

Alright now let’s try to run with some additional words to enhance our prompt.

Prompt #2

“I want to build a serverless rest api on AWS using serverless framework only, please don't use any AWS SAM. There should be 3 Lambda functions for create, update, and delete. Each Lambda function should have an HTTP event which is either post, put, or delete with cognito authentication attached. The Lambda functions should use the latest NodeJS runtime supported and not typescript. The DynamoDB table should use pay per request instead of provisioned throughput. There should be a AWS Cognito User Pool.

Please generate the above in YAML format.”

Okay so ChatGPT went the route of using AWS SAM again with the Lambda function component, however I’m happy to see that it did take my feedback around DynamoDB.

  
resources:
  Resources:
    UsersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: users
        BillingMode: PAY_PER_REQUEST
        AttributeDefinitions:
          - AttributeName: userId
            AttributeType: S
        KeySchema:
          - AttributeName: userId
            KeyType: HASH
  

It also took my feedback around the functions just not in the serverless framework way.

  
resources:
  Resources:
    CreateFunction:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: .
        Handler: create.handler
        Runtime: nodejs14.x
        Timeout: 30
        Environment:
          USERS_TABLE: !Ref UsersTable
          USER_POOL_ID: !Ref UserPool
        Events:
          CreateEvent:
            Type: Http
            Properties:
              Path: /users
              Method: post
              RestApiId: !Ref ApiGateway
              Auth:
                Authorizer: !Ref CognitoAuthorizer

    UpdateFunction:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: .
        Handler: update.handler
        Runtime: nodejs14.x
        Timeout: 30
        Environment:
          USERS_TABLE: !Ref UsersTable
          USER_POOL_ID: !Ref UserPool
        Events:
          UpdateEvent:
            Type: Http
            Properties:
              Path: /users/{id}
              Method: put
              RestApiId: !Ref ApiGateway
              Auth:
                Authorizer: !Ref CognitoAuthorizer

    DeleteFunction:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: .
        Handler: delete
  

As you can see it took the advice and created three AWS Lambda functions, but again the ChatGPT network crashed again. I am determined though, so let’s update slightly and re-run.

Prompt #3

“I want to build a serverless rest api on AWS using serverless framework only, please don't use any AWS SAM. There should be 3 Lambda functions for create, update, and delete. Each Lambda function should have an HTTP event which is either post, put, or delete with cognito authentication attached. The Lambda functions should use the latest NodeJS runtime supported and not typescript. The DynamoDB table should use pay per request instead of provisioned throughput. There should be a AWS Cognito User Pool.

If you can separate the resources into multiple different serverless.yml files that would be ideal. The first serverless.yml file should include AWS API Gateway and Lambda, the second should include only DynamoDB, and the third should include the Cognito User Pool.

Please generate the above in YAML format.”

The new sentence added is asking for ChatGPT to give me multiple files versus just one.

“If you can separate the resources into multiple different serverless.yml files that would be ideal. The first serverless.yml file should include AWS API Gateway and Lambda, the second should include only DynamoDB, and the third should include the Cognito User Pool.”

Okay now we are cooking. Check this out.

  
service: my-rest-api
provider:
  name: aws
  runtime: nodejs14.x
  region: us-east-1

plugins:
  - serverless-offline

functions:
  create:
    handler: create.handler
    events:
      - http:
          path: /create
          method: post
          cors: true
          authorizer:
            arn: arn:aws:cognito-idp:${self:provider.region}:#{AWS::AccountId}:userpool/${self:custom.userPoolId}
  update:
    handler: update.handler
    events:
      - http:
          path: /update
          method: put
          cors: true
          authorizer:
            arn: arn:aws:cognito-idp:${self:provider.region}:#{AWS::AccountId}:userpool/${self:custom.userPoolId}
  delete:
    handler: delete.handler
    events:
      - http:
          path: /delete
          method: delete
          cors: true
          authorizer:
            arn: arn:aws:cognito-idp:${self:provider.region}:#{AWS::AccountId}:userpool/${self:custom.userPoolId}

custom:
  userPoolId: ${cf:userPool.Id}
  

This is an awesome result 👏👏 Okay so stack #1 is looking pretty amazing. Also ChatGPT gave us some extra information.

I have no complaints this is a great breakdown of what’s happening!

  
# serverless-dynamodb.yml

service: my-rest-api-dynamodb
provider:
  name: aws
  runtime: nodejs14.x
  region: us-east-1

resources:
  Resources:
    myTable:
      Type: AWS::DynamoDB::Table
      Properties:
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST
        TableName: my-table-name
  

Again 👏 👏  Exactly what we are looking for with a caveat being that the name of the table is not exported out of the stack and the naming doesn’t include the service name or stage so we have an easy time as we promote through different stages e.g. dev, qa, prod. If all tables are named my-table-name then it will block future deployments.

It’s a good additional breakdown from ChatGPT, not much to say.

  
# serverless-cognito.yml

service: my-rest-api-cognito
provider:
  name: aws
  runtime: nodejs14.x
  region: us-east-1

resources:
  Resources:
    userPool:
      Type: AWS::Cognito::UserPool
      Properties:
        UserPoolName: my-user-pool-name
        UsernameAttributes:
          - email
        AutoVerifiedAttributes:
          - email
        Policies:
          PasswordPolicy:
            MinimumLength: 8
            RequireLowercase: true
            RequireNumbers: true
            RequireSymbols: true
            RequireUppercase: true
        AdminCreateUserConfig:
          AllowAdminCreateUserOnly: false
        EmailConfiguration:
          EmailSendingAccount
  

Okay and the third stack looks okay. However the EmailConfiguration section is incomplete and it doesn’t show that the ChatGPT network crashed. Let’s ignore that.

My crtique here is that in the first stack we are trying to pull in the Cognito User Pool ID, however it’s not Output from this stack. This could be because it silently broke under the hood, but I’ll try to ask for just the Cognito stack and see what comes back.

“Can you regenerate the third cognito user pool serverless.yml file which outputs the cognito user pool id?”

Alright here is the result.

  
# serverless-cognito.yml

service: my-rest-api-cognito
provider:
  name: aws
  runtime: nodejs14.x
  region: us-east-1

resources:
  Resources:
    userPool:
      Type: AWS::Cognito::UserPool
      Properties:
        UserPoolName: my-user-pool-name
        UsernameAttributes:
          - email
        AutoVerifiedAttributes:
          - email
        Policies:
          PasswordPolicy:
            MinimumLength: 8
            RequireLowercase: true
            RequireNumbers: true
            RequireSymbols: true
            RequireUppercase: true
        AdminCreateUserConfig:
          AllowAdminCreateUserOnly: false
        EmailConfiguration:
          EmailSendingAccount: DEVELOPER

  Outputs:
    Id:
      Value:
        Ref: userPool
      Export:
        Name: my-rest-api-user-pool-id
  

It did what I asked, however, I don’t believe the Outputs export name will line up with the first file, but that’s something an experienced serverless developer could easily modify.

Conclusion using ChatGPT 3.5

Alright let’s rank this from 1/10 where 1 is the worst and 10 is the best. I would rank ChatGPT ability to generate pretty complex serverless.yml IaC (infrastructure-as-code) at about 7/10 or 8/10. If you have the right prompt and you know how to ask for things. Then it can really accelerate things. I could keep sharpening the prompt to potentially take it from 7/10 or 8/10 to 9/10 with updated information past 2021 and the ability to feed existing serverless framework templates. I think 9/10 is possible.

If you don’t know how to ask things and you don’t have a fairly complex prompt then 3/10 would be my rating.

Past the point of generating the files, there is a bunch of additional complex prompts that you likely need around deployment, IAM setup, CI/CD setup, etc. But, I’m fairly confident with the right words you could do it.

Continue reading to see how ChatGPT 4 performs, short answer, nuts.

ChatGPT 4

ChatGPT 4 came out on March 14th and I wrote the article on March 15th, but apparently I can already access by purchasing ChatGPT Plus. It’s later in the afternoon on March 15th, I’ve just bought ChatGPT Plus and now I’ll try the same prompt again as I did before and see what comes back if any differences.

“I want to build a serverless rest api on AWS using serverless framework only, please don't use any AWS SAM. There should be 3 Lambda functions for create, update, and delete. Each Lambda function should have an HTTP event which is either post, put, or delete with cognito authentication attached. The Lambda functions should use the latest NodeJS runtime supported and not typescript. The DynamoDB table should use pay per request instead of provisioned throughput. There should be a AWS Cognito User Pool.

If you can separate the resources into multiple different serverless.yml files that would be ideal. The first serverless.yml file should include AWS API Gateway and Lambda, the second should include only DynamoDB, and the third should include the Cognito User Pool.

Please generate the above in YAML format.”

First stack, serverless-api-gateway-lambda.yml

  
service: serverless-api-gateway-lambda

provider:
  name: aws
  runtime: nodejs14.x
  region: us-east-1

functions:
  create:
    handler: handler.create
    events:
      - http:
          path: /create
          method: post
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId: !Ref CognitoUserPool
  update:
    handler: handler.update
    events:
      - http:
          path: /update
          method: put
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId: !Ref CognitoUserPool
  delete:
    handler: handler.delete
    events:
      - http:
          path: /delete
          method: delete
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId: !Ref CognitoUserPool

resources:
  Outputs:
    ApiGatewayRestApiId:
      Description: API Gateway REST API ID
      Value: !Ref ApiGatewayRestApi
      Export:
        Name: ApiGatewayRestApiId
  

I’m actually in favor of the ChatGPT 3.5 response here. As the authorizer section is not actually accurate, it’s referencing a resource which doesn’t exist inside this file and is not leveraging AWS CloudFormation Imports/Outputs and since we don’t have a separate API Gateway serverless.yml we don’t need to output the ApiGatewayRestApiId.

  
authorizer:
  type: COGNITO_USER_POOLS
  authorizerId: !Ref CognitoUserPool
  

No feedback on the DynamoDB stack, looks good 👌

  
service: serverless-dynamodb

provider:
  name: aws
  region: us-east-1

resources:
  Resources:
    MyDynamoDBTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: MyTable
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST
  

Now for the Cognito User Pool serverless.yml.

  
service: serverless-cognito

provider:
  name: aws
  region: us-east-1

resources:
  Resources:
    CognitoUserPool:
      Type: AWS::Cognito::UserPool
      Properties:
        UserPoolName: MyUserPool
        Schema:
          - Name: email
            AttributeDataType: String
            Mutable: true
            Required: true
        Policies:
          PasswordPolicy:
            MinimumLength: 8
            RequireUppercase: true
            RequireLowercase: true
            RequireNumbers: true
            RequireSymbols: true
    CognitoUserPoolClient:
      Type: AWS::Cognito::UserPoolClient
      Properties:
        ClientName: MyUserPoolClient
        UserPoolId: !Ref CognitoUserPool
        ExplicitAuthFlows:
          - ADMIN_NO_SRP_AUTH
        GenerateSecret: false

  Outputs:
    CognitoUserPoolId:
      Description: Cognito User Pool ID
      Value: !Ref CognitoUserPool
      Export:
        Name: CognitoUserPoolId
    CognitoUserPoolClientId:
      Description: Cognito User Pool Client ID
      Value: !Ref CognitoUserPoolClient
      Export:
        Name: CognitoUserPoolClientId
  

The outputs look good on this, I’ll try to ask ChatGPT to update the serverless-api-gateway-lambda serverless.yml to use AWS CloudFormation Imports for the CognitoUserPoolId.

Prompt #2 with ChatGPT 4

“Can you regenerate the serverless-api-gateway-lambda serverless.yml to use AWS CloudFormation Imports for the CognitoUserPoolId”

Wow very impressive, check it out.

  
service: serverless-api-gateway-lambda

provider:
  name: aws
  runtime: nodejs14.x
  region: us-east-1

functions:
  create:
    handler: handler.create
    events:
      - http:
          path: /create
          method: post
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId:
              Fn::ImportValue: CognitoUserPoolId
  update:
    handler: handler.update
    events:
      - http:
          path: /update
          method: put
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId:
              Fn::ImportValue: CognitoUserPoolId
  delete:
    handler: handler.delete
    events:
      - http:
          path: /delete
          method: delete
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId:
              Fn::ImportValue: CognitoUserPoolId
  

Now the functions block has been modified to use the export name. The issue will come up later with the naming not being dynamic enough especially with the introduction of new stages. Let’s see if we can get ChatGPT to add a naming convention in the serverless framework way.

Prompt #3 ChatGPT 4

“Can you regenerate all three serverless.yml files with a better naming convention that uses service name and stage in the dynamic serverless framework syntax?”

Alright this result is amazing.

  
service: serverless-api-gateway-lambda

provider:
  name: aws
  runtime: nodejs14.x
  region: us-east-1
  stage: ${opt:stage, 'dev'}

functions:
  create:
    handler: handler.create
    events:
      - http:
          path: /create
          method: post
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId:
              Fn::ImportValue: ${self:service}-${self:provider.stage}-CognitoUserPoolId
  update:
    handler: handler.update
    events:
      - http:
          path: /update
          method: put
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId:
              Fn::ImportValue: ${self:service}-${self:provider.stage}-CognitoUserPoolId
  delete:
    handler: handler.delete
    events:
      - http:
          path: /delete
          method: delete
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId:
              Fn::ImportValue: ${self:service}-${self:provider.stage}-CognitoUserPoolId
  

ChatGPT 4 is definitely taking the cake, the previous input was reused perfectly and it was able to include the dynamic Serverless Framework syntax as well. Crazy and it did it for all three serverless.yml files. Let’s look at the output of the Cognito serverless.yml.

  
Outputs:
  CognitoUserPoolId:
    Description: Cognito User Pool ID
    Value: !Ref CognitoUserPool
    Export:
      Name: ${self:service}-${self:provider.stage}-CognitoUserPoolId
  CognitoUserPoolClientId:
    Description: Cognito User Pool Client ID
    Value: !Ref CognitoUserPoolClient
    Export:
      Name: ${self:service}-${self:provider.stage}-CognitoUserPoolClientId
  

As you can see it’s updated the Export Name as well which will line up perfectly with our serverless-api-gateway-lambda serverless.yml. Now let’s generate some NodeJS for our handlers.

Prompt #4 ChatGPT 4

“Can you generate some NodeJS 14 lambda code which handles create, update, and delete in the same handler file to the dynamodb table from serverless-dynamodb.yml?”

Holy cow, I’m completely blown away by this result. Take a look.

  
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();

const tableName = `${process.env.SERVICE_NAME}-${process.env.STAGE}-MyTable`;

const response = (statusCode, body) => ({
  statusCode,
  body: JSON.stringify(body),
});

exports.create = async (event) => {
  const { id, ...data } = JSON.parse(event.body);

  if (!id) {
    return response(400, { message: 'Missing id in request' });
  }

  const item = {
    id,
    ...data,
  };

  try {
    await dynamo.put({ TableName: tableName, Item: item }).promise();
    return response(200, item);
  } catch (error) {
    console.error(error);
    return response(500, { message: 'Error creating item' });
  }
};

exports.update = async (event) => {
  const { id, ...data } = JSON.parse(event.body);

  if (!id) {
    return response(400, { message: 'Missing id in request' });
  }

  const updateExpression = Object.keys(data)
    .map((key) => `${key} = :${key}`)
    .join(', ');
  const expressionAttributeValues = Object.fromEntries(
    Object.entries(data).map(([key, value]) => [`:${key}`, value])
  );

  try {
    await dynamo
      .update({
        TableName: tableName,
        Key: { id },
        UpdateExpression: `set ${updateExpression}`,
        ExpressionAttributeValues: expressionAttributeValues,
        ReturnValues: 'ALL_NEW',
      })
      .promise();
    return response(200, { id, ...data });
  } catch (error) {
    console.error(error);
    return response(500, { message: 'Error updating item' });
  }
};

exports.delete = async (event) => {
  const { id } = event.pathParameters;

  if (!id) {
    return response(400, { message: 'Missing id in request' });
  }

  try {
    await dynamo.delete({ TableName: tableName, Key: { id } }).promise();
    return response(200, { message: 'Item deleted successfully' });
  } catch (error) {
    console.error(error);
    return response(500, { message: 'Error deleting item' });
  }
};
  

It’s done exactly what I asked, but gone much further adding in error handling, using async/await, etc. Pretty amazing. ChatGPT also added that we need to update our serverless.yml file to feed in the environment variables for SERVICE_NAME and STAGE which is cool. It even shortened the code to speed up adding it in.

  
functions:
  create:
    handler: handler.create
    environment:
      SERVICE_NAME: ${self:service}
      STAGE: ${self:provider.stage}
    events:
      # ...
  update:
    handler: handler.update
    environment:
      SERVICE_NAME: ${self:service}
      STAGE: ${self:provider.stage}
    events:
      # ...
  delete:
    handler: handler.delete
    environment:
      SERVICE_NAME: ${self:service}
      STAGE: ${self:provider.stage}
    events:
      # ...
  

As you can see above ChatGPT added # … as a placeholder to save space. We could take this further and ask ChatGPT to pass the environment variables globally and create the dynamic table name under custom instead of passing multiple times in each function block.

Prompt #5 ChatGPT 4

“Can you pass the environment variables globally and create the dynamic table name under custom instead of passing multiple times in each function block?”

Well it almost did what I wanted.

  
provider:
  ...
  environment:
    SERVICE_NAME: ${self:service}
    STAGE: ${self:provider.stage}
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:PutItem
            - dynamodb:UpdateItem
            - dynamodb:DeleteItem
          Resource: !Join [":", [!GetAtt MyDynamoDBTable.Arn, "/*"]]

custom:
  tableName: ${self:service}-${self:provider.stage}-MyTable

...

resources:
  Resources:
    MyDynamoDBTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:custom.tableName}
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST
  

As you can see it’s now updated the serverless.yml with the lambdas to include an iam role to the DynamoDB table which is great, but it’s added the DynamoDB table into that same serverless.yml file. That being said it did still do what I asked.

As you can see it’s given me the updated import for my Lambda function handler and the updated functions block section. Let’s ask it to leave the dynamodb table out of the serverless-api-gateway-lambda serverless.yml while still generating the IAM permissions required to access DynamoDB for create, update, and delete using AWS CloudFormation Imports.

Prompt #6 ChatGPT 4

“Can you leave the dynamodb table out of the serverless-api-gateway-lambda serverless.yml while still generating the IAM permissions required to access DynamoDB for create, update, and delete using AWS CloudFormation Imports?”

The result is stellar, check this out.

  
service: serverless-api-gateway-lambda

provider:
  name: aws
  runtime: nodejs14.x
  region: us-east-1
  stage: ${opt:stage, 'dev'}
  environment:
    SERVICE_NAME: ${self:service}
    STAGE: ${self:provider.stage}
    TABLE_NAME: ${self:custom.tableName}
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:PutItem
            - dynamodb:UpdateItem
            - dynamodb:DeleteItem
          Resource:
            Fn::Join:
              - ""
              - - "arn:aws:dynamodb:"
                - Ref: AWS::Region
                - ":"
                - Ref: AWS::AccountId
                - ":table/"
                - Fn::ImportValue: ${self:custom.tableName}-DynamoDBTableArn
                - "/*"

custom:
  tableName: ${self:service}-${self:provider.stage}-MyTable

functions:
  create:
    handler: handler.create
    events:
      - http:
          path: /create
          method: post
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId:
              Fn::ImportValue: ${self:service}-${self:provider.stage}-CognitoUserPoolId
  update:
    handler: handler.update
    events:
      - http:
          path: /update
          method: put
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId:
              Fn::ImportValue: ${self:service}-${self:provider.stage}-CognitoUserPoolId
  delete:
    handler: handler.delete
    events:
      - http:
          path: /delete
          method: delete
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId:
              Fn::ImportValue: ${self:service}-${self:provider.stage}-CognitoUserPoolId
  

This is quite the complex setup!

  
provider:
  ...
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:PutItem
            - dynamodb:UpdateItem
            - dynamodb:DeleteItem
          Resource:
            Fn::Join:
              - ""
              - - "arn:aws:dynamodb:"
                - Ref: AWS::Region
                - ":"
                - Ref: AWS::AccountId
                - ":table/"
                - Fn::ImportValue: ${self:custom.tableName}-DynamoDBTableArn
                - "/*"
  

ChatGPT has given us the proper permissons to the proper table and leveraged AWS CloudFormation Imports to enhance further. ChatGPT has also given us the outputs needed for the DynamoDB serverless.yml.

Now for final measure let’s ask ChatGPT how we should organize these files locally and how we should deploy them to AWS.

Prompt #6 ChatGPT 4

“how should I organize these files locally and how we should deploy them to AWS?”

This is the recommendation from ChatGPT 4 on file organization.

No real complaints here, simple and effective. It sets up for a monorepo structure with the my-api root folder which is a common setup.

Initial setup steps

Deployment sequence, this is actually pretty amazing. ChatGPT knows that the deployment needs to happen in a specific way because of the AWS CloudFormation Imports/Outputs!

Finally it flexs even more on us by talking about the deploy function command which allows us to deploy a single function versus the entire serverless.yml file.

Because I’m greedy, let’s ask ChatGPT to give us a script we can include in our NodeJS package.json to handle deploying all of the serverless.yml files which has a flag of stage and region.

Prompt #7 ChatGPT 4

“Can you give me a script I can include in our NodeJS package.json to handle deploying all of the serverless.yml files which has a flag of stage and region?”

Okay this is wild stuff.

  
const { exec } = require('child_process');
const path = require('path');
const argv = require('minimist')(process.argv.slice(2));

const stage = argv.stage || 'dev';
const region = argv.region || 'us-east-1';

const services = [
  'cognito',
  'dynamodb',
  'api-gateway-lambda',
];

const deployService = (service) => {
  return new Promise((resolve, reject) => {
    const command = `serverless deploy -s ${stage} -r ${region} -c ${path.join(__dirname, service, `serverless-${service}.yml`)}`;
    const child = exec(command, { cwd: path.join(__dirname, service) });

    child.stdout.pipe(process.stdout);
    child.stderr.pipe(process.stderr);

    child.on('close', (code) => {
      if (code === 0) {
        resolve();
      } else {
        reject(new Error(`Service ${service} failed to deploy with exit code ${code}`));
      }
    });
  });
};

(async () => {
  try {
    for (const service of services) {
      console.log(`Deploying ${service}...`);
      await deployService(service);
      console.log(`${service} deployed successfully.`);
    }
  } catch (error) {
    console.error(`Error deploying services: ${error.message}`);
    process.exit(1);
  }
})();
  

Now I don’t know if it works, but I’m assuming even if it doesn’t we can modify it slightly. This is pretty amazing and likely would take even an experienced Senior Developer quite a bit of time to write out or without ChatGPT many hours to google, test, tweak, etc.

Again ChatGPT is flexing on us with this description.

And with that, I’m going to wrap it up.

Conclusion

Alright let’s rank this from 1/10 where 1 is the worst and 10 is the best. I would rank ChatGPT 4 ability to generate pretty complex serverless.yml IaC (infrastructure-as-code) at about 9/10 or 10/10.

If you don’t know how to ask things and you don’t have a fairly complex prompt then 6/10 or 7/10 would be my rating. It will give you something that works, but if you don’t know that it needs to be improved further than you may stop short before you get a real magical result.

ChatGPT 4 is definitely a game changer, a lot of the stuff being generated through these prompts would take unexperienced serverless developers quite a few hours if not days to wire together. For a seasoned serverless developer that knows how to communicate and spot gaps, it can easily save hours of work.

Now what does this mean?

In my opinion, it’s still a tool, a really powerful tool. However, you need a human to wield it. With an experienced human, cough Serverless Guru, you could build so much stuff in record speed. It’s quite astonishing and I’m excited and a bit nervous to see how this keeps evolving!

Thanks for reading and have a wonderful day :)

Access free book

The dream team

At Serverless Guru, we're a collective of proactive solution finders. We prioritize genuineness, forward-thinking vision, and above all, we commit to diligently serving our members each and every day.

See open positions

Looking for skilled architects & developers?

Join businesses around the globe that trust our services. Let's start your serverless journey. Get in touch today!
Ryan Jones
Founder
Speak to a Guru
Edu Marcos
Chief Technology Officer
Speak to a Guru
Mason Toberny
Head of Enterprise Accounts
Speak to a Guru

Join the Community

Gather, share, and learn about AWS and serverless with enthusiasts worldwide in our open and free community.