Wednesday, August 3, 2022
HomeWordPress DevelopmentConstruct a Serverless URL shortener with Go

Construct a Serverless URL shortener with Go


Utilizing AWS Lambda, DynamoDB and API Gateway

This weblog submit covers the best way to construct a Serverless URL shortener software utilizing Go. It leverages AWS Lambda for enterprise logic, DynamoDB for persistence and API Gateway to supply the HTTP endpoints to entry and use the appliance. The pattern software offered on this weblog is a trimmed down model of bit.ly or different options you’ll have used or encountered.

It is structured as follows:

  • I’ll begin off with a fast introduction and dive into the best way to deploy attempt the answer.
  • After that, I’ll concentrate on the code itself. This can cowl:
    • The half which is used to put in writing the infrastructure (utilizing Go bindings for AWS CDK)
    • And in addition the core enterprise logic which comprises the Lambda perform (utilizing Lambda Go assist) in addition to the DynamoDB operations (utilizing the DynamoDB Go SDK)

On this weblog, you’ll be taught:

  • Find out how to use the DynamoDB Go SDK (v2) to execute CRUD operations resembling PutItem, GetItem, UpdateItem and DeleteItem
  • Find out how to use AWS CDK Go bindings to deploy a Serverless software to create and handle a DynamoDB desk, Lambda features, API Gateway and different elements as nicely.

When you deploy the appliance, it is possible for you to to create brief codes for URLs utilizing the endpoint uncovered by the API Gateway and in addition entry them.

# create brief code for a URL (e.g. https://abhirockzz.github.io/)
curl -i -X POST -d 'https://abhirockzz.github.io/' -H 'Content material-Sort: textual content/plain' $URL_SHORTENER_APP_URL

# entry URL by way of brief code
curl -i $URL_SHORTENER_APP_URL/<short-code>
Enter fullscreen mode

Exit fullscreen mode



Lets get began – Deploy the Serverless software

Earlier than you proceed, be sure you have the Go programming language (v1.16 or greater) and AWS CDK put in.

Clone the undertaking and alter to the appropriate listing:

git clone https://github.com/abhirockzz/serverless-url-shortener-golang
cd cdk
Enter fullscreen mode

Exit fullscreen mode

To begin the deployment…

.. all you’ll do is run a single command (cdk deploy), and watch for a bit. You will note a (lengthy) record of assets that can be created and might want to present your affirmation to proceed.

Don’t be concerned, within the subsequent part I’ll clarify what’s occurring.

cdk deploy

# output

Bundling asset ServerlessURLShortenerStack1/create-url-function/Code/Stage...
Bundling asset ServerlessURLShortenerStack1/access-url-function/Code/Stage...
Bundling asset ServerlessURLShortenerStack1/update-url-status-function/Code/Stage...
Bundling asset ServerlessURLShortenerStack1/delete-url-function/Code/Stage...

✨  Synthesis time: 10.28s

This deployment will make probably delicate modifications based on your present safety approval degree (--require-approval broadening).
Please affirm you propose to make the next modifications:
.......

Do you want to deploy these modifications (y/n)?
Enter fullscreen mode

Exit fullscreen mode

This can begin creating the AWS assets required for our software.

If you wish to see the AWS CloudFormation template which can be used behind the scenes, run cdk synth and verify the cdk.out folder

You possibly can preserve monitor of the progress within the terminal or navigate to AWS console: CloudFormation > Stacks > ServerlessURLShortenerStack

Image description

As soon as all of the assets are created, you’ll be able to check out the appliance. It’s best to have:

  • 4 Lambda features (and associated assets)
  • A DynamoDB desk
  • An API Gateway (in addition to routes, integrations)
  • together with a couple of others (like IAM roles and many others.)

Earlier than you proceed, get the API Gateway endpoint that you’ll want to make use of. It is out there within the stack output (within the terminal or the Outputs tab within the AWS CloudFormation console in your Stack):

Image description



Shorten some URLs!

Begin by producing brief codes for a couple of URLs

# export the API Gateway endpoint
export URL_SHORTENER_APP_URL=<exchange with apigw endpoint above>

# for instance:
export URL_SHORTENER_APP_URL=https://b3it0tltzk.execute-api.us-east-1.amazonaws.com/

# invoke the endpoint to create brief codes
curl -i -X POST -d 'https://abhirockzz.github.io/' -H 'Content material-Sort: textual content/plain' $URL_SHORTENER_APP_URL

curl -i -X POST -d 'https://dzone.com/customers/456870/abhirockzz.html' -H 'Content material-Sort: textual content/plain' $URL_SHORTENER_APP_URL

curl -i -X POST -d 'https://abhishek1987.medium.com/' -H 'Content material-Sort: textual content/plain' $URL_SHORTENER_APP_URL
Enter fullscreen mode

Exit fullscreen mode

To generate a brief code, you must go the unique URL within the payload physique as a part of a HTTP POST request (for e.g. https://abhishek1987.medium.com/)

‘Content material-Sort: textual content/plain’ is vital, in any other case API Gateway will do base64 encoding of your payload

If all goes nicely, you need to get a HTTP 201 together with the brief code within the HTTP response (as a JSON payload).

HTTP/2 201 
date: Fri, 15 Jul 2022 13:03:20 GMT
content-type: textual content/plain; charset=utf-8
content-length: 25
apigw-requestid: VTzPsgmSoAMESdA=

{"short_code":"1ee3ad1b"}
Enter fullscreen mode

Exit fullscreen mode

To substantiate, verify the DynamoDB desk.

Image description

Discover an lively attribute there? Extra on this quickly

Entry the URL utilizing the brief code

With companies like bit.ly and many others. you sometimes create brief hyperlinks in your URLs and share them with the world. We are going to do one thing comparable. Now that you’ve the brief code generated, you’ll be able to share the hyperlink (it is probably not a brief hyperlink like bit.ly however that is okay for now!) with others and as soon as they entry it, they’d see the unique URL.

The entry hyperlink can have the next format – <URL_SHORTENER_APP_URL>/<generated brief code> for e.g. https://b3it0tltzk.execute-api.us-east-1.amazonaws.com/1ee3ad1b

If you happen to navigate to the hyperlink utilizing a browser, you can be mechanically redirected to the unique URL that you simply had specified. To see what is going on on, attempt the identical with curl:

curl -i $URL_SHORTENER_APP_URL/<brief code>

# instance
curl -i https://b3it0tltzk.execute-api.us-east-1.amazonaws.com/0e1785b1
Enter fullscreen mode

Exit fullscreen mode

That is merely an HTTP GET request. If all goes nicely, you need to get an HTTP 302 response (StatusFound) and the URL re-direction occurs because of the the Location HTTP header which comprises the unique URL.

HTTP/2 302 
date: Fri, 15 Jul 2022 13:08:54 GMT
content-length: 0
location: https://abhirockzz.github.io/
apigw-requestid: VT0D1hNLIAMES8w=
Enter fullscreen mode

Exit fullscreen mode

How about utilizing a brief code that doesn’t exist?

Set the standing

You possibly can allow and disable the brief codes. The unique URL will solely be accessible if the affiliation is in lively state.

To disable a brief code:

curl -i -X PUT -d '{"lively": false}'  -H 'Content material-Sort: software/json' $URL_SHORTENER_APP_URL/<brief code>

# instance
curl -i -X PUT -d '{"lively": false}'  -H 'Content material-Sort: software/json' https://b3it0tltzk.execute-api.us-east-1.amazonaws.com/1ee3ad1b
Enter fullscreen mode

Exit fullscreen mode

That is an HTTP PUT request with a JSON payload that specifies the standing (false on this case refers to disable) together with the brief code which is a path parameter to the API Gateway endpoint. If all works nicely, you need to see a HTTP 204 (No Content material) response:

HTTP/2 204 
date: Fri, 15 Jul 2022 13:15:41 GMT
apigw-requestid: VT1Digy8IAMEVHw=
Enter fullscreen mode

Exit fullscreen mode

Test the DynamoDB report – the lively attribute will need to have switched to false.

As an train, attempt the next:

  1. entry the URL by way of the identical brief code now and verify the response.
  2. entry an invalid brief code i.e. that doesn’t exist
  3. allow a disabled URL (use {"lively": true})

Okay, thus far now we have coated all operations, besides delete. Lets attempt that and wrap up the CRUD!

Delete

curl -i -X DELETE $URL_SHORTENER_APP_URL/<brief code>

# instance
curl -i -X DELETE https://b3it0tltzk.execute-api.us-east-1.amazonaws.com/1ee3ad1b
Enter fullscreen mode

Exit fullscreen mode

Nothing too stunning. We use a HTTP DELETE together with the brief code. Similar to in case of replace, you need to get a HTTP 204 response:

HTTP/2 204 
date: Fri, 15 Jul 2022 13:23:36 GMT
apigw-requestid: VT2NzgjnIAMEVKA=
Enter fullscreen mode

Exit fullscreen mode

However this time in fact, the DynamoDB report ought to have been deleted – affirm the identical.

What occurs whenever you attempt to delete a brief code that doesn’t exist?



Do not forget to scrub up!

When you’re carried out, to delete all of the companies, merely use:

cdk destroy
Enter fullscreen mode

Exit fullscreen mode

Alright, now that you have truly seen “what” the appliance does, let’s transfer on to the “how”.
We are going to begin with the AWS CDK code and discover the way it does all of the heavy lifting behind to setup the infrastructure for our Serverless URL shortener service.



With AWS CDK, Infrastructure-IS-code!

You possibly can try the code in this GitHub repo. I’ll stroll you thru the keys elements of the NewServerlessURLShortenerStack perform which defines the workhorse of our CDK software.

I’ve omitted a number of the code for brevity

We begin by making a DynamoDB desk. A main secret’s all that is required with a purpose to do this – on this case shortcode (we do not have vary/kind key on this instance)

    dynamoDBTable := awsdynamodb.NewTable(stack, jsii.String("url-shortener-dynamodb-table"),
        &awsdynamodb.TableProps{
            PartitionKey: &awsdynamodb.Attribute{
                Identify: jsii.String(shortCodeDynamoDBAttributeName),
                Sort: awsdynamodb.AttributeType_STRING}})
Enter fullscreen mode

Exit fullscreen mode

Then, we create an API Gateway (HTTP API) with only one line of code!

urlShortenerAPI := awscdkapigatewayv2alpha.NewHttpApi(stack, jsii.String("url-shortner-http-api"), nil)
Enter fullscreen mode

Exit fullscreen mode

We transfer on to the primary Lambda perform that creates brief codes for URLs. Discover that we use an experimental module awscdklambdagoalpha (right here is the steady model on the time of writing). In case your Go undertaking is structured in a selected method (particulars right here) and also you specify its path utilizing Entry, it would mechanically maintain constructing, packaging and deploying your Lambda perform! Not unhealthy in any respect!

Along with Native bundling (as used on this instance), Docker based mostly builds are additionally supported.

    createURLFunction := awscdklambdagoalpha.NewGoFunction(stack, jsii.String("create-url-function"),
        &awscdklambdagoalpha.GoFunctionProps{
            Runtime:     awslambda.Runtime_GO_1_X(),
            Surroundings: funcEnvVar,
            Entry:       jsii.String(createShortURLFunctionDirectory)})

    dynamoDBTable.GrantWriteData(createURLFunction)
Enter fullscreen mode

Exit fullscreen mode

Lastly, we add the final little bit of plumbing by making a Lambda-HTTP API integration (discover how the Lambda perform variable createURLFunction is referenced) and including a path to the HTTP API we had created – this in flip refers back to the Lambda integration.

    createFunctionIntg := awscdkapigatewayv2integrationsalpha.NewHttpLambdaIntegration(jsii.String("create-function-integration"), createURLFunction, nil)

    urlShortenerAPI.AddRoutes(&awscdkapigatewayv2alpha.AddRoutesOptions{
        Path:        jsii.String("https://dev.to/"),
        Strategies:     &[]awscdkapigatewayv2alpha.HttpMethod{awscdkapigatewayv2alpha.HttpMethod_POST},
        Integration: createFunctionIntg})
Enter fullscreen mode

Exit fullscreen mode

This was only for one perform – now we have three extra remaining. The nice half is that the template for all these are comparable i.e.

  1. create the perform
  2. grant permission for DynamoDB
  3. wire it up with API Gateway (with the proper HTTP technique i.e. POST, PUT, DELETE)

So I can’t repeat it over right here. Be at liberty to grok by way of the remainder of the code.

Now that you simply perceive the magic behind the “one-click” infrastructure setup, let’s transfer on to the core logic of the appliance.



URL shortener Lambda perform and DynamoDB logic

There are 4 totally different features, all of that are of their respective folders and all of them have a couple of issues in frequent in the way in which they function:

  1. They do dome preliminary processing – course of the payload, or extract the trail parameter from the URL and many others.
  2. Invoke a frequent database layer – to execute the CRUD performance (extra on this quickly)
  3. Deal with errors as applicable and return response

With that data, it must be simple to comply with alongside the code.

As earlier than, some elements of the code have been omitted for brevity

Create perform

func handler(ctx context.Context, req occasions.APIGatewayV2HTTPRequest) (occasions.APIGatewayV2HTTPResponse, error) {
    url := req.Physique

    shortCode, err := db.SaveURL(url)
    if err != nil {//..deal with error}

    response := Response{ShortCode: shortCode}
    respBytes, err := json.Marshal(response)
    if err != nil {//..deal with error}

    return occasions.APIGatewayV2HTTPResponse{StatusCode: http.StatusCreated, Physique: string(respBytes)}, nil
}
Enter fullscreen mode

Exit fullscreen mode

This perform begins by studying the payload of the HTTP request physique – it is a string which has the URL for which the brief code is being created. It invokes the database layer to attempt to save this report to DynamoDB and handles errors. Lastly, it returns a JSON response with the brief code.

Right here is the perform that truly interacts with DynamoDB to get the job carried out.

func SaveURL(longurl string) (string, error) {
    shortCode := uuid.New().String()[:8]

    merchandise := make(map[string]sorts.AttributeValue)

    merchandise[longURLDynamoDBAttributeName] = &sorts.AttributeValueMemberS{Worth: longurl}
    merchandise[shortCodeDynamoDBAttributeName] = &sorts.AttributeValueMemberS{Worth: shortCode}
    merchandise[activeDynamoDBAttributeName] = &sorts.AttributeValueMemberBOOL{Worth: true}

    _, err := shopper.PutItem(context.Background(), &dynamodb.PutItemInput{
        TableName: aws.String(desk),
        Merchandise:      merchandise})

    if err != nil {//..deal with error}

    return shortCode, nil
}
Enter fullscreen mode

Exit fullscreen mode

For the needs of this pattern app, the brief code is created by producing a UUID and trimming out the final 8 digits. It is easy to exchange this with one other approach – all that issues is that you simply generate a singular string that may work as a brief code. Then, all of it about calling the PutItem API with the required knowledge.

Entry the URL

func handler(ctx context.Context, req occasions.APIGatewayV2HTTPRequest) (occasions.APIGatewayV2HTTPResponse, error) {

    shortCode := req.PathParameters[pathParameterName]
    longurl, err := db.GetLongURL(shortCode)

    if err != nil {//..deal with error}

    return occasions.APIGatewayV2HTTPResponse{StatusCode: http.StatusFound, Headers: map[string]string{locationHeader: longurl}}, nil
}
Enter fullscreen mode

Exit fullscreen mode

When somebody accesses the brief hyperlink (as demonstrated within the earlier part), the brief code is handed in as a path parameter e.g. http://<api gw url>/<brief code>. the database layer is invoked to get the corresponding URL from DynamoDB desk (errors are dealt with as wanted). Lastly, the response is returned to the person whereby the standing code is 302 and the URL is handed within the Location header. That is what re-directs you to the unique URL whenever you enter the brief code (within the browser)

Right here is the DynamoDB name:

func GetLongURL(shortCode string) (string, error) {

    op, err := shopper.GetItem(context.Background(), &dynamodb.GetItemInput{
        TableName: aws.String(desk),
        Key: map[string]sorts.AttributeValue{
            shortCodeDynamoDBAttributeName: &sorts.AttributeValueMemberS{Worth: shortCode}}})

    if err != nil {//..deal with error}

    if op.Merchandise == nil {
        return "", ErrUrlNotFound
    }

    activeAV := op.Merchandise[activeDynamoDBAttributeName]
    lively := activeAV.(*sorts.AttributeValueMemberBOOL).Worth

    if !lively {
        return "", ErrUrlNotActive
    }

    longurlAV := op.Merchandise[longURLDynamoDBAttributeName]
    longurl := longurlAV.(*sorts.AttributeValueMemberS).Worth

    return longurl, nil
}
Enter fullscreen mode

Exit fullscreen mode

Step one is to make use of GetItem API to get the DynamoDB report containing URL and standing equivalent to the brief code. If the merchandise object within the response is nil, we are able to make certain that a report with that brief code does not exist – we return a customized error which may be useful for our perform which might then return an applicable response to the caller of the API (e.g. a HTTP 404). We additionally verify the standing (lively or not) and return an error if lively is about to false. If all is nicely, the URL is returned to the caller.

Replace standing

func handler(ctx context.Context, req occasions.APIGatewayV2HTTPRequest) (occasions.APIGatewayV2HTTPResponse, error) {

    var payload Payload
    reqBody := req.Physique

    err := json.Unmarshal([]byte(reqBody), &payload)
    if err != nil {//..deal with error}

    shortCode := req.PathParameters[pathParameterName]

    err = db.Replace(shortCode, payload.Energetic)
    if err != nil {//..deal with error}

    return occasions.APIGatewayV2HTTPResponse{StatusCode: http.StatusNoContent}, nil
}
Enter fullscreen mode

Exit fullscreen mode

Step one is to marshal the HTTP request payload which is a JSON e.g. {"lively": false} after which get the brief code from the trail parameter. The database layer is invoked to replace the standing and deal with errors.

func Replace(shortCode string, standing bool) error {

    replace := expression.Set(expression.Identify(activeDynamoDBAttributeName), expression.Worth(standing))
    updateExpression, _ := expression.NewBuilder().WithUpdate(replace).Construct()

    situation := expression.AttributeExists(expression.Identify(shortCodeDynamoDBAttributeName))
    conditionExpression, _ := expression.NewBuilder().WithCondition(situation).Construct()

    _, err := shopper.UpdateItem(context.Background(), &dynamodb.UpdateItemInput{
        TableName: aws.String(desk),
        Key: map[string]sorts.AttributeValue{
            shortCodeDynamoDBAttributeName: &sorts.AttributeValueMemberS{Worth: shortCode}},
        UpdateExpression:          updateExpression.Replace(),
        ExpressionAttributeNames:  updateExpression.Names(),
        ExpressionAttributeValues: updateExpression.Values(),
        ConditionExpression:       conditionExpression.Situation(),
    })

    if err != nil && strings.Comprises(err.Error(), "ConditionalCheckFailedException") {
        return ErrUrlNotFound
    }

    return err
}
Enter fullscreen mode

Exit fullscreen mode

The UpdateItem API name takes care of fixing the standing. It is pretty easy aside from the all these expressions that you simply want – particularly when you’re new to the idea. The primary one (necessary) is the replace expression the place you specify the attribute you must set (lively on this case) and its worth. The second makes certain that you’re updating the standing for a brief code that truly exists within the desk. That is vital since, in any other case the UpdateItem API name will insert a new merchandise (we do not need that!). As a substitute of rolling out the expressions by hand, we use the expressions package deal.

Delete brief code

func handler(ctx context.Context, req occasions.APIGatewayV2HTTPRequest) (occasions.APIGatewayV2HTTPResponse, error) {

    shortCode := req.PathParameters[pathParameterName]

    err := db.Delete(shortCode)
    if err != nil {//..deal with error}
    return occasions.APIGatewayV2HTTPResponse{StatusCode: http.StatusNoContent}, nil
}
Enter fullscreen mode

Exit fullscreen mode

The delete handler is not any totally different. After the brief code to be deleted is extracted from the trail parameter, the database layer is invoked to take away it from the DynamoDB desk. The consequence returned to the person is both an HTTP 204 (on success) or the error.

func Delete(shortCode string) error {

    situation := expression.AttributeExists(expression.Identify(shortCodeDynamoDBAttributeName))
    conditionExpression, _ := expression.NewBuilder().WithCondition(situation).Construct()

    _, err := shopper.DeleteItem(context.Background(), &dynamodb.DeleteItemInput{
        TableName: aws.String(desk),
        Key: map[string]sorts.AttributeValue{
            shortCodeDynamoDBAttributeName: &sorts.AttributeValueMemberS{Worth: shortCode}},
        ConditionExpression:       conditionExpression.Situation(),
        ExpressionAttributeNames:  conditionExpression.Names(),
        ExpressionAttributeValues: conditionExpression.Values()})

    if err != nil && strings.Comprises(err.Error(), "ConditionalCheckFailedException") {
        return ErrUrlNotFound
    }

    return err
}
Enter fullscreen mode

Exit fullscreen mode

Similar to UpdateItem API, the DeleteItem API additionally takes in a situation expression. If there isn’t any report within the DynamoDB desk with the given brief code, an error is returned. In any other case, the report is deleted.

That completes the code stroll by way of!



Wrap up

On this weblog submit you learnt the best way to use DynamoDB Go SDK utilizing a URL Shortener pattern software. You additionally built-in it with AWS Lambda and API Gateway to construct a Serverless answer whose infrastructure was additionally outlined utilizing precise code (versus yaml, JSON and many others.), because of the Go assist in AWS CDK.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments