├── README.md ├── build └── main.zip ├── cmd └── main.go ├── go.mod ├── go.sum └── pkg ├── handlers ├── api_response.go └── handlers.go ├── user └── user.go └── validators └── is_email_valid.go /README.md: -------------------------------------------------------------------------------- 1 | # Golang-Serverless-Project 2 | API Gateway + DynamoDB + Lambda 3 | Comlpete serveless stack 4 | -------------------------------------------------------------------------------- /build/main.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkhilSharma90/Golang-Serverless-Project/6b6d0ea3654ff78a7818043969ae2eb11316f9b1/build/main.zip -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import( 4 | "github.com/akhil/go-serverless-yt/pkg/handlers" 5 | "os" 6 | "github.com/aws/aws-lambda-go/events" 7 | "github.com/aws/aws-lambda-go/lambda" 8 | "github.com/aws/aws-sdk-go/aws" 9 | "github.com/aws/aws-sdk-go/aws/session" 10 | "github.com/aws/aws-sdk-go/service/dynamodb" 11 | "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface" 12 | ) 13 | 14 | var( 15 | dynaClient dynamodbiface.DynamoDBAPI 16 | ) 17 | 18 | func main(){ 19 | region := os.Getenv("AWS_REGION") 20 | awsSession, err := session.NewSession(&aws.Config{ 21 | Region: aws.String(region)},) 22 | 23 | if err!=nil{ 24 | return 25 | } 26 | dynaClient = dynamodb.New(awsSession) 27 | lambda.Start(handler) 28 | } 29 | 30 | 31 | const tableName = "go-serverless-yt" 32 | 33 | func handler(req events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error){ 34 | switch req.HTTPMethod{ 35 | case "GET": 36 | return handlers.GetUser(req, tableName, dynaClient) 37 | case "POST": 38 | return handlers.CreateUser(req, tableName, dynaClient) 39 | case "PUT": 40 | return handlers.UpdateUser(req, tableName, dynaClient) 41 | case "DELETE": 42 | return handlers.DeleteUser(req, tableName, dynaClient) 43 | default: 44 | return handlers.UnhandledMethod() 45 | } 46 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/akhil/go-serverless-yt 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/aws/aws-lambda-go v1.27.0 7 | github.com/aws/aws-sdk-go v1.42.8 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/aws/aws-lambda-go v1.27.0 h1:aLzrJwdyHoF1A18YeVdJjX8Ixkd+bpogdxVInvHcWjM= 3 | github.com/aws/aws-lambda-go v1.27.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= 4 | github.com/aws/aws-sdk-go v1.42.8 h1:Tj2RP4Fas1mYchwbmw0qWLJIEATAseyp5iTa1D+LWYQ= 5 | github.com/aws/aws-sdk-go v1.42.8/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= 6 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 12 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 13 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 14 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 15 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 19 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 20 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 21 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 22 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 23 | github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= 24 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 25 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 26 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 28 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 29 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 30 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 31 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 32 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 33 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 34 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 35 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= 36 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 37 | -------------------------------------------------------------------------------- /pkg/handlers/api_response.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import( 4 | "encoding/json" 5 | "github.com/aws/aws-lambda-go/events" 6 | ) 7 | 8 | func apiResponse(status int, body interface{})(*events.APIGatewayProxyResponse, error){ 9 | resp := events.APIGatewayProxyResponse{Headers: map[string]string{"Content-Type":"application/json"}} 10 | resp.StatusCode = status 11 | 12 | stringBody, _ := json.Marshal(body) 13 | resp.Body = string(stringBody) 14 | return &resp, nil 15 | } -------------------------------------------------------------------------------- /pkg/handlers/handlers.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import( 4 | "github.com/akhil/go-serverless-yt/pkg/user" 5 | "net/http" 6 | "github.com/aws/aws-lambda-go/events" 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface" 9 | 10 | ) 11 | 12 | var ErrorMethodNotAllowed = "method not allowed" 13 | 14 | type ErrorBody struct{ 15 | ErrorMsg *string `json:"error,omitempty"` 16 | } 17 | 18 | func GetUser(req events.APIGatewayProxyRequest, tableName string, dynaClient dynamodbiface.DynamoDBAPI)( 19 | *events.APIGatewayProxyResponse, error, 20 | ){ 21 | 22 | email := req.QueryStringParameters["email"] 23 | if len(email) > 0 { 24 | result, err := user.FetchUser(email, tableName, dynaClient) 25 | if err!= nil { 26 | return apiResponse(http.StatusBadRequest, ErrorBody{aws.String(err.Error())}) 27 | } 28 | return apiResponse(http.StatusOK, result) 29 | } 30 | 31 | result, err := user.FetchUsers(tableName, dynaClient) 32 | if err!= nil { 33 | return apiResponse(http.StatusBadRequest, ErrorBody{ 34 | aws.String(err.Error()), 35 | }) 36 | } 37 | return apiResponse(http.StatusOK, result) 38 | 39 | } 40 | 41 | func CreateUser(req events.APIGatewayProxyRequest, tableName string, dynaClient dynamodbiface.DynamoDBAPI)( 42 | *events.APIGatewayProxyResponse, error, 43 | ){ 44 | result, err := user.CreateUser(req, tableName, dynaClient) 45 | if err!=nil { 46 | return apiResponse(http.StatusBadRequest, ErrorBody{ 47 | aws.String(err.Error()), 48 | }) 49 | } 50 | return apiResponse(http.StatusCreated, result) 51 | } 52 | 53 | func UpdateUser(req events.APIGatewayProxyRequest, tableName string, dynaClient dynamodbiface.DynamoDBAPI)( 54 | *events.APIGatewayProxyResponse, error, 55 | ){ 56 | result, err := user.UpdateUser(req, tableName, dynaClient) 57 | if err!= nil { 58 | return apiResponse(http.StatusBadRequest, ErrorBody{ 59 | aws.String(err.Error()), 60 | }) 61 | } 62 | return apiResponse(http.StatusOK, result) 63 | } 64 | 65 | func DeleteUser(req events.APIGatewayProxyRequest, tableName string, dynaClient dynamodbiface.DynamoDBAPI)( 66 | *events.APIGatewayProxyResponse, error, 67 | ){ 68 | err := user.DeleteUser(req, tableName, dynaClient) 69 | 70 | if err!= nil { 71 | return apiResponse(http.StatusBadRequest, ErrorBody{ 72 | aws.String(err.Error()), 73 | }) 74 | } 75 | return apiResponse(http.StatusOK, nil) 76 | } 77 | 78 | func UnhandledMethod()(*events.APIGatewayProxyResponse, error){ 79 | return apiResponse(http.StatusMethodNotAllowed, ErrorMethodNotAllowed) 80 | } -------------------------------------------------------------------------------- /pkg/user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import( 4 | "github.com/akhil/go-serverless-yt/pkg/validators" 5 | "encoding/json" 6 | "errors" 7 | "github.com/aws/aws-lambda-go/events" 8 | "github.com/aws/aws-sdk-go/aws" 9 | "github.com/aws/aws-sdk-go/service/dynamodb" 10 | "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" 11 | "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface" 12 | ) 13 | 14 | var( 15 | ErrorFailedToUnmarshalRecord = "failed to unmarshal record" 16 | ErrorFailedToFetchRecord = "failed to fetch record" 17 | ErrorInvalidUserData = "invalid user data" 18 | ErrorInvalidEmail = "invalid email" 19 | ErrorCouldNotMarshalItem = "could not marshal item" 20 | ErrorCouldNotDeleteItem = "could not delete item" 21 | ErrorCouldNotDynamoPutItem = "could not dynamo put item" 22 | ErrorUserAlreadyExists = "user.User already exists" 23 | ErrorUserDoesNotExist = "user.User does not exist" 24 | ) 25 | 26 | type User struct{ 27 | Email string `json:"email"` 28 | FirstName string `json:"firstName"` 29 | LastName string `json:"lastName"` 30 | } 31 | 32 | func FetchUser(email, tableName string, dynaClient dynamodbiface.DynamoDBAPI)(*User, error){ 33 | 34 | input := &dynamodb.GetItemInput{ 35 | Key: map[string]*dynamodb.AttributeValue{ 36 | "email":{ 37 | S: aws.String(email), 38 | }, 39 | }, 40 | TableName: aws.String(tableName), 41 | } 42 | 43 | result, err := dynaClient.GetItem(input) 44 | if err!= nil { 45 | return nil, errors.New(ErrorFailedToFetchRecord) 46 | } 47 | 48 | item := new(User) 49 | err = dynamodbattribute.UnmarshalMap(result.Item, item) 50 | if err != nil { 51 | return nil, errors.New(ErrorFailedToUnmarshalRecord) 52 | } 53 | return item, nil 54 | } 55 | 56 | func FetchUsers(tableName string, dynaClient dynamodbiface.DynamoDBAPI)(*[]User, error){ 57 | input := &dynamodb.ScanInput{ 58 | TableName: aws.String(tableName), 59 | } 60 | 61 | result, err := dynaClient.Scan(input) 62 | if err!= nil { 63 | return nil, errors.New(ErrorFailedToFetchRecord) 64 | } 65 | item := new([]User) 66 | err = dynamodbattribute.UnmarshalListOfMaps(result.Items, item) 67 | return item, nil 68 | } 69 | 70 | func CreateUser(req events.APIGatewayProxyRequest, tableName string, dynaClient dynamodbiface.DynamoDBAPI)( 71 | *User, 72 | error, 73 | ){ 74 | var u User 75 | 76 | if err := json.Unmarshal([]byte(req.Body), &u); err!=nil { 77 | return nil, errors.New(ErrorInvalidUserData) 78 | } 79 | if !validators.IsEmailValid(u.Email){ 80 | return nil, errors.New(ErrorInvalidEmail) 81 | } 82 | 83 | currentUser, _ := FetchUser(u.Email, tableName, dynaClient) 84 | if currentUser != nil && len(currentUser.Email) != 0 { 85 | return nil, errors.New(ErrorUserAlreadyExists) 86 | } 87 | 88 | av, err := dynamodbattribute.MarshalMap(u) 89 | 90 | if err != nil { 91 | return nil, errors.New(ErrorCouldNotMarshalItem) 92 | } 93 | 94 | input := &dynamodb.PutItemInput{ 95 | Item: av, 96 | TableName: aws.String(tableName), 97 | } 98 | 99 | _, err = dynaClient.PutItem(input) 100 | if err != nil { 101 | return nil, errors.New(ErrorCouldNotDynamoPutItem) 102 | } 103 | return &u, nil 104 | } 105 | 106 | func UpdateUser(req events.APIGatewayProxyRequest, tableName string, dynaClient dynamodbiface.DynamoDBAPI)( 107 | *User, 108 | error, 109 | ){ 110 | var u User 111 | if err := json.Unmarshal([]byte(req.Body), &u); err !=nil { 112 | return nil, errors.New(ErrorInvalidEmail) 113 | } 114 | 115 | currentUser, _ := FetchUser(u.Email, tableName, dynaClient) 116 | if currentUser !=nil && len (currentUser.Email) == 0{ 117 | return nil, errors.New(ErrorUserDoesNotExist) 118 | } 119 | 120 | av, err := dynamodbattribute.MarshalMap(u) 121 | if err != nil { 122 | return nil, errors.New(ErrorCouldNotMarshalItem) 123 | } 124 | 125 | input := &dynamodb.PutItemInput{ 126 | Item: av, 127 | TableName: aws.String(tableName), 128 | } 129 | 130 | _, err = dynaClient.PutItem(input) 131 | if err!=nil { 132 | return nil, errors.New(ErrorCouldNotDynamoPutItem) 133 | } 134 | return &u, nil 135 | } 136 | 137 | func DeleteUser(req events.APIGatewayProxyRequest, tableName string, dynaClient dynamodbiface.DynamoDBAPI) error{ 138 | 139 | 140 | email := req.QueryStringParameters["email"] 141 | input := &dynamodb.DeleteItemInput{ 142 | Key: map[string]*dynamodb.AttributeValue{ 143 | "email":{ 144 | S: aws.String(email), 145 | }, 146 | }, 147 | TableName: aws.String(tableName), 148 | } 149 | _, err := dynaClient.DeleteItem(input) 150 | if err != nil { 151 | return errors.New(ErrorCouldNotDeleteItem) 152 | } 153 | 154 | return nil 155 | } -------------------------------------------------------------------------------- /pkg/validators/is_email_valid.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import "regexp" 4 | 5 | func IsEmailValid(email string) bool { 6 | var rxEmail = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]{1,64}@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") 7 | 8 | if len(email) < 3 || len(email) > 254 || !rxEmail.MatchString(email){ 9 | return false 10 | } 11 | 12 | return true 13 | } --------------------------------------------------------------------------------