How to Use AWS DynamoDB with Go: A Practical Guide with Localstack
golang docker aws dynamodb localstackIntroduction
AWS DynamoDB is a highly scalable, fast, and flexible NoSQL database that is a great choice for applications that require low latency and high throughput. In this tutorial, we will show you how to use AWS DynamoDB with Go and localstack to test our code. This guide will provide you with practical examples and explanations that will make it easy for you to use DynamoDB in your Go applications.
Prerequisites
Before we start, ensure that you have the following installed and set up:
- Go programming language
- AWS CLI
- Localstack
Understanding AWS DynamoDB with Go
Before we get started with the code, let’s first understand the basics of DynamoDB and how it works with Go. DynamoDB is a fully managed, serverless database that provides consistent, fast performance with seamless scalability. It is a key-value and document database that delivers single-digit millisecond performance at any scale.
Go is a simple and powerful open-source programming language that is designed for building fast, efficient, and scalable applications. It is a perfect language for building cloud-native applications, and its simplicity and performance make it a great choice for working with AWS services like DynamoDB.
Setting up Localstack
Localstack is a tool that enables you to emulate AWS services on your local machine. It is a great tool for testing your applications without incurring any costs. To set up Localstack, follow these steps:
- Install Docker on your machine if you haven’t already.
- Pull the Localstack image from Docker Hub using the command
docker pull localstack/localstack
. - Run the Localstack container with the command
docker run -p 4566:4566 localstack/localstack
.
Creating a DynamoDB Table
In this section, we will create a DynamoDB table using the AWS SDK for Go. Ensure that you have set up your AWS credentials by running aws configure
. For Localstack, use any 12 digit number (like 112233445566
) as AWS Access Key ID and same 12 digit number as AWS Secret Access Key since Localstack ignores the secret anyways. Default region name will be us-east-1
and Default output format will be json
.
package main
import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
type Employee struct {
ID int
Name string
Age int
}
func main() {
sess, err := session.NewSession(&aws.Config{
Region: aws.String("us-east-1"),
Endpoint: aws.String("http://localhost:4566"),
})
if err != nil {
log.Fatalf("failed to start new session: %v", err)
}
svc := dynamodb.New(sess)
createTable(svc)
}
func createTable(svc *dynamodb.DynamoDB) {
input := &dynamodb.CreateTableInput{
AttributeDefinitions: []*dynamodb.AttributeDefinition{
{AttributeName: aws.String("ID"), AttributeType: aws.String("N")},
},
KeySchema: []*dynamodb.KeySchemaElement{
{AttributeName: aws.String("ID"), KeyType: aws.String("HASH")},
},
ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
ReadCapacityUnits: aws.Int64(5),
WriteCapacityUnits: aws.Int64(5),
},
TableName: aws.String("Employee"),
}
if _, err := svc.CreateTable(input); err != nil {
log.Fatalf("failed to create table: %v", err)
}
fmt.Println("Table created successfully")
}
/*
$ go run main.go
Table created successfully
*/
In this code snippet, we define a struct called Employee
and use the dynamodbattribute
package to marshal and unmarshal data between Go and DynamoDB. We then create a new session and configure it to use Localstack. We create a new dynamodb
service and use the CreateTable
method to create a new table called “Employee” with an ID attribute that is a number.
Inserting Data into the Table
Now that we have created a table, we can insert data into it.
package main
import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)
func insert(svc *dynamodb.DynamoDB) {
item := Employee{
ID: 1,
Name: "John Doe",
Age: 30,
}
av, err := dynamodbattribute.MarshalMap(item)
if err != nil {
log.Fatalf("failed to marshal item: %v", err)
}
input := &dynamodb.PutItemInput{
Item: av,
TableName: aws.String("Employee"),
}
if _, err := svc.PutItem(input); err != nil {
log.Fatalf("failed to put item: %v", err)
}
fmt.Println("Item inserted successfully")
}
/*
$ go run main.go insert.go
Item inserted successfully
*/
In this code snippet, we create a new Employee
struct and marshal it into a DynamoDB attribute value map. We then use the PutItem
method to insert the item into the “Employee” table.
Querying Data from the Table
Now that we have inserted data into the table, we can query it using the Query
method.
package main
import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)
func query(svc *dynamodb.DynamoDB) {
input := &dynamodb.QueryInput{
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":val": {
N: aws.String("1"),
},
},
KeyConditionExpression: aws.String("ID = :val"),
TableName: aws.String("Employee"),
}
result, err := svc.Query(input)
if err != nil {
log.Fatalf("failed to query: %v", err)
return
}
for _, item := range result.Items {
emp := Employee{}
err = dynamodbattribute.UnmarshalMap(item, &emp)
if err != nil {
log.Fatalf("failed to unmarshal item: %v", err)
return
}
fmt.Printf("%+v\\n", emp)
}
}
/*
$ go run main.go query.go
{ID:1 Name:John Doe Age:30}\n%
/*
In this code snippet, we use the Query
method to query the “Employee” table for all items with an ID of 1. We then unmarshal the results into a Employee
struct and print it to the console.
Updating Data in the Table
To update data in the table, we use the UpdateItem
method.
package main
import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
func update(svc *dynamodb.DynamoDB) {
input := &dynamodb.UpdateItemInput{
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":n": {
S: aws.String("Jane Doe"),
},
},
TableName: aws.String("Employee"),
Key: map[string]*dynamodb.AttributeValue{
"ID": {
N: aws.String("1"),
},
},
UpdateExpression: aws.String("set #name = :n"),
ExpressionAttributeNames: map[string]*string{
"#name": aws.String("Name"),
},
}
if _, err := svc.UpdateItem(input); err != nil {
log.Fatalf("failed to update item: %v", err)
}
fmt.Println("Item updated successfully")
}
/*
$ go run main.go update.go
Item updated successfully
*/
In this code snippet, we use the UpdateItem
method to update the name of the employee with ID 1. We use the ExpressionAttributeValues
parameter to define the new name, and the UpdateExpression
parameter to specify that we want to update the Name
attribute. We also use the ExpressionAttributeNames
parameter to specify that Name
is a reserved keyword and should be escaped with a #
character.
Deleting Data from the Table
To delete data from the table, we use the DeleteItem
method.
package main
import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
func delete(svc *dynamodb.DynamoDB) {
input := &dynamodb.DeleteItemInput{
TableName: aws.String("Employee"),
Key: map[string]*dynamodb.AttributeValue{
"ID": {
N: aws.String("1"),
},
},
}
if _, err := svc.DeleteItem(input); err != nil {
log.Fatalf("failed to delete item: %v", err)
}
fmt.Println("Item deleted successfully")
}
/*
$ go run main.go delete.go
Item deleted successfully
*/
In this code snippet, we use the DeleteItem
method to delete the employee with ID 1 from the “Employee” table.
Querying Data with Sort Keys
DynamoDB is a powerful NoSQL database that supports querying data using sort keys. Sort keys allow you to sort and filter your data based on a specific criterion.
package main
import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)
type Employee struct {
ID int
Name string
Age int
City string
Country string
}
func main() {
sess, err := session.NewSession(&aws.Config{
Region: aws.String("us-east-1"),
Endpoint: aws.String("http://localhost:4566"),
})
if err != nil {
log.Fatalf("failed to create new session: %v", err)
}
svc := dynamodb.New(sess)
input := &dynamodb.CreateTableInput{
AttributeDefinitions: []*dynamodb.AttributeDefinition{
{AttributeName: aws.String("ID"), AttributeType: aws.String("N")},
{AttributeName: aws.String("Name"), AttributeType: aws.String("S")},
{AttributeName: aws.String("City"), AttributeType: aws.String("S")},
},
KeySchema: []*dynamodb.KeySchemaElement{
{AttributeName: aws.String("ID"), KeyType: aws.String("HASH")},
{AttributeName: aws.String("Name"), KeyType: aws.String("RANGE")},
},
GlobalSecondaryIndexes: []*dynamodb.GlobalSecondaryIndex{
{
IndexName: aws.String("CityIndex"),
KeySchema: []*dynamodb.KeySchemaElement{
{AttributeName: aws.String("ID"), KeyType: aws.String("HASH")},
{AttributeName: aws.String("City"), KeyType: aws.String("RANGE")},
},
Projection: &dynamodb.Projection{
ProjectionType: aws.String("ALL"),
},
ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
ReadCapacityUnits: aws.Int64(5),
WriteCapacityUnits: aws.Int64(5),
},
},
},
ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
ReadCapacityUnits: aws.Int64(5),
WriteCapacityUnits: aws.Int64(5),
},
TableName: aws.String("Employee2"),
}
if _, err := svc.CreateTable(input); err != nil {
log.Fatalf("failed to create table: %v", err)
}
fmt.Println("Table created successfully")
item := Employee{
ID: 1,
Name: "John Doe",
Age: 30,
City: "New York",
Country: "USA",
}
av, err := dynamodbattribute.MarshalMap(item)
if err != nil {
log.Fatalf("failed to marshal item: %v", err)
}
input2 := &dynamodb.PutItemInput{
Item: av,
TableName: aws.String("Employee2"),
}
if _, err := svc.PutItem(input2); err != nil {
log.Fatalf("failed to insert item: %v", err)
}
fmt.Println("Item inserted successfully")
input3 := &dynamodb.QueryInput{
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":id": {N: aws.String("1")},
":val": {S: aws.String("New York")},
},
KeyConditionExpression: aws.String("ID = :id and begins_with(City, :val)"),
TableName: aws.String("Employee2"),
IndexName: aws.String("CityIndex"),
}
result, err := svc.Query(input3)
if err != nil {
log.Fatalf("failed to query table: %v", err)
}
for _, item := range result.Items {
emp := Employee{}
if err := dynamodbattribute.UnmarshalMap(item, &emp); err != nil {
log.Fatalf("failed to unmarshal item: %v", err)
}
fmt.Printf("%+v\\n", emp)
}
}
/*
$ go run sortkeys/main.go
Table created successfully
Item inserted successfully
{ID:1 Name:John Doe Age:30 City:New York Country:USA}\n%
*/
In this code snippet, we create a new DynamoDB table called “Employee2” and insert a new item into it. We then use the Query
method with a begins_with
function to query the data based on the City
attribute. We also define a new index called “CityIndex” and use it to optimize the query. This example demonstrates how you can use sort keys to filter your data and optimize your queries.
Conclusion
In this tutorial, we have shown you how to use AWS DynamoDB with Go and Localstack to test your code. We have covered the basic database operations including creating a table, inserting data into it, querying data from it, updating data, and deleting data. We have also demonstrated how to use sort keys to filter your data and optimize your queries. With this knowledge, you can now integrate DynamoDB into your Go applications and build scalable, high-performance applications that can handle any workload. Happy coding!
In case you would like to get notified about more articles like this, please subscribe to my blog. The full source code is available in the Github repository https://github.com/abvarun226/blog-source-code/tree/master/how-to-use-aws-dynamodb-with-go-a-practical-guide-with-localstack