diff --git a/dictionary.txt b/dictionary.txt
index ab5cf9f2b..765163b14 100644
--- a/dictionary.txt
+++ b/dictionary.txt
@@ -216,6 +216,7 @@ reproducibility
misconfigurations
DSL
UI
+init
^.+[-:_]\w+$
[a-z]+([A-Z0-9]|[A-Z0-9]\w+)
diff --git a/docs/guides/go/realtime-messaging.mdx b/docs/guides/go/realtime-messaging.mdx
new file mode 100644
index 000000000..89f37aae9
--- /dev/null
+++ b/docs/guides/go/realtime-messaging.mdx
@@ -0,0 +1,300 @@
+---
+description: Use the Nitric framework to easily build and deploy Go WebSocket applications for AWS.
+tags:
+ - Realtime & Websockets
+ - Key Value Store
+---
+
+# Building a chat app in Go with WebSockets and Nitric
+
+## What we'll be doing
+
+1. Use Nitric to create a WebSocket API
+2. Manage WebSocket connections using a Key-Value store
+3. Handle WebSocket events:
+ - Register connections on connect
+ - Remove connections on disconnect
+ - Broadcast messages to all connected clients
+4. Run locally for testing
+5. Deploy to AWS
+
+## Prerequisites
+
+- [Go](https://go.dev/dl/)
+- The [Nitric CLI](/get-started/installation)
+- An [AWS](https://aws.amazon.com) account (optional)
+
+## Getting started
+
+We'll start by creating a new project for our WebSocket application. The finished source can be found [here](https://github.com/nitrictech/examples/tree/main/v1/websocket-app).
+
+```bash
+nitric new websocket-app go-starter
+```
+
+Next, open the project in your editor of choice.
+
+```bash
+cd websocket-app
+```
+
+Make sure all dependencies are resolved:
+
+```bash
+go mod tidy
+```
+
+The scaffolded project should have the following structure:
+
+```text
++--services/
+| +-- hello/
+| +-- main.go
++--nitric.yaml
++--go.mod
++--go.sum
++--golang.dockerfile
++--.gitignore
++--README.md
+```
+
+You can test the project to verify everything is working as expected:
+
+```bash
+nitric start
+```
+
+If everything is working as expected, you can now delete all files/folders in the `services/` folder. We'll create new services in this guide.
+
+## Building the WebSocket Application
+
+Let's begin by setting up the WebSocket application. Add a file named `main.go` to your 'services/websockets' folder, and include the following code:
+
+```go title:services/websockets/main.go
+package main
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/nitrictech/go-sdk/nitric"
+ "github.com/nitrictech/go-sdk/nitric/keyvalue"
+ "github.com/nitrictech/go-sdk/nitric/websockets"
+)
+
+func main() {
+ // Create a WebSocket API named "public".
+ ws := nitric.NewWebsocket("public")
+
+ // Initialize a KV store named "connections" with Get, Set, and Delete permissions.
+ connections := nitric.NewKv("connections").Allow(keyvalue.KvStoreGet, keyvalue.KvStoreSet, keyvalue.KvStoreDelete)
+
+ // Add event handlers here
+
+ nitric.Run()
+}
+```
+
+Here we're creating:
+
+- A [WebSocket](/websockets) API named `public`
+- A [Key-Value store](/keyvalue) named `connections` to track client connections
+
+From here, let's add some features to that function that allow us to manage connections and broadcast messages.
+
+
+ You could separate some or all of these event handlers into their own services
+ if you prefer. For simplicity, we'll group them together in this guide.
+
+
+### Register connections on connect
+
+```go
+ws.On(websockets.EventType_Connect, func(ctx *websockets.Ctx) {
+ err := connections.Set(context.Background(), ctx.Request.ConnectionID(), map[string]interface{}{
+ "connectionId": ctx.Request.ConnectionID(),
+ })
+ if err != nil {
+ fmt.Println("Error storing connection ID in KV store:", err)
+ ctx.Response.Reject = true
+ }
+})
+```
+
+### Remove connections on disconnect
+
+```go
+ws.On(websockets.EventType_Disconnect, func(ctx *websockets.Ctx) {
+ err := connections.Delete(context.Background(), ctx.Request.ConnectionID())
+ if err != nil {
+ fmt.Println("Error deleting connection ID in KV store:", err)
+ return
+ }
+})
+```
+
+### Broadcast messages to all connected clients
+
+```go
+ws.On(websockets.EventType_Message, func(ctx *websockets.Ctx) {
+ connectionStream, err := connections.Keys(context.Background())
+ if err != nil {
+ fmt.Println("Error retrieving connection keys from KV store:", err)
+ return
+ }
+
+ senderId := ctx.Request.ConnectionID()
+
+ for {
+ connectionId, err := connectionStream.Recv()
+ if err != nil {
+ break
+ }
+
+ if connectionId == senderId {
+ continue
+ }
+
+ message := fmt.Sprintf("%s: %s", senderId, ctx.Request.Message())
+ err = ws.Send(context.Background(), connectionId, []byte(message))
+ if err != nil {
+ fmt.Println("Error sending message to connection ID", connectionId, ":", err)
+ return
+ }
+ }
+})
+```
+
+### Bringing it all together
+
+
+Your code should look like this:
+
+```go title:services/websockets/main.go
+package main
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/nitrictech/go-sdk/nitric"
+ "github.com/nitrictech/go-sdk/nitric/keyvalue"
+ "github.com/nitrictech/go-sdk/nitric/websockets"
+)
+
+func main() {
+ // Create a WebSocket endpoint named "public".
+ ws := nitric.NewWebsocket("public")
+
+ // Initialize a KV store named "connections" with Get, Set, and Delete permissions.
+ connections := nitric.NewKv("connections").Allow(keyvalue.KvStoreGet, keyvalue.KvStoreSet, keyvalue.KvStoreDelete)
+
+ // Handle new WebSocket connections by storing the connection ID in the KV store.
+ ws.On(websockets.EventType_Connect, func(ctx *websockets.Ctx) {
+ err := connections.Set(context.Background(), ctx.Request.ConnectionID(), map[string]interface{}{
+ "connectionId": ctx.Request.ConnectionID(),
+ })
+ if err != nil {
+ fmt.Println("Error storing connection ID in KV store:", err)
+ ctx.Response.Reject = true
+ }
+ })
+
+ ws.On(websockets.EventType_Disconnect, func(ctx *websockets.Ctx) {
+ err := connections.Delete(context.Background(), ctx.Request.ConnectionID())
+ if err != nil {
+ fmt.Println("Error deleting connection ID in KV store:", err)
+ return
+ }
+ })
+
+ ws.On(websockets.EventType_Message, func(ctx *websockets.Ctx) {
+ connectionStream, err := connections.Keys(context.Background())
+ if err != nil {
+ fmt.Println("Error retrieving connection keys from KV store:", err)
+ return
+ }
+
+ senderId := ctx.Request.ConnectionID()
+
+ for {
+ connectionId, err := connectionStream.Recv()
+ if err != nil {
+ break
+ }
+
+ if connectionId == senderId {
+ continue
+ }
+
+ message := fmt.Sprintf("%s: %s", senderId, ctx.Request.Message())
+ err = ws.Send(context.Background(), connectionId, []byte(message))
+ if err != nil {
+ fmt.Println("Error sending message to connection ID", connectionId, ":", err)
+ return
+ }
+ }
+ })
+
+ nitric.Run()
+}
+```
+
+
+
+Do a quick `go mod tidy` to make sure all new dependencies are resolved.
+
+## Ok, let's run this thing!
+
+Now that you have your WebSocket application defined with handlers for each event, it's time to test it locally.
+
+```bash
+nitric start
+```
+
+Once it starts, the application will be ready to accept WebSocket connections that you can easily test with the [Nitric Dashboard](/get-started/foundations/projects/local-development#local-dashboard). You can find the URL to the dashboard in the terminal running the Nitric CLI, by default, it is set to - http://localhost:49152.
+
+![websocket dashboard](/docs/images/guides/realtime-messaging/dashboard.png)
+
+The dashboard will show you the WebSocket URL and allow you to connect as a client to send, receive and monitor messages.
+
+## Deploy to the cloud
+
+At this point, you can deploy what you've built to any of the supported cloud providers. In this example we'll deploy to AWS. Start by setting up your credentials and configuration for the [nitric/aws provider](/providers/pulumi/aws).
+
+Next, we'll need to create a `stack file` (deployment target). A stack is a deployed instance of an application. You might want separate stacks for each environment, such as stacks for `dev`, `test`, and `prod`. For now, let's start by creating a file for the `dev` stack.
+
+The `stack new` command below will create a stack named `dev` that uses the `aws` provider.
+
+```bash
+nitric stack new dev aws
+```
+
+Edit the stack file `nitric.dev.yaml` and set your preferred AWS region, for example `us-east-1`.
+
+### AWS
+
+
+ You are responsible for staying within the limits of the free tier or any
+ costs associated with deployment.
+
+
+Let's try deploying the stack with the `up` command:
+
+```bash
+nitric up
+```
+
+When the deployment is complete, go to the relevant cloud console and you'll be able to see and interact with your WebSocket application.
+
+To tear down your application from the cloud, use the `down` command:
+
+```bash
+nitric down
+```
+
+## Summary
+
+In this guide, we've created a serverless WebSocket application using Go and Nitric. We've demonstrated how to set up WebSocket connections, track clients using a Key-Value store, and broadcast messages to all connected clients. This application can be easily deployed to the cloud, allowing you to build scalable, real-time communication systems.
+
+For more information and advanced usage, refer to the [Nitric documentation](https://nitric.io/docs).
diff --git a/docs/guides/python/graphql.mdx b/docs/guides/python/graphql.mdx
index 170378098..09d100489 100644
--- a/docs/guides/python/graphql.mdx
+++ b/docs/guides/python/graphql.mdx
@@ -5,8 +5,6 @@ tags:
- API
---
-export const title_meta = 'Building a GraphQL API with Python and Nitric'
-
# Building a GraphQL API with Nitric
## What we'll be doing
diff --git a/docs/guides/terraform/terratest.mdx b/docs/guides/terraform/terratest.mdx
new file mode 100644
index 000000000..f92c0c1d0
--- /dev/null
+++ b/docs/guides/terraform/terratest.mdx
@@ -0,0 +1,256 @@
+---
+description: Use Terratest to validate the infrastructure of a Nitric Go project deployed with Terraform
+tags:
+ - Terraform
+ - Testing
+---
+
+# Testing AWS resources with Terratest
+
+This guide will walk you through adding [Terratest](https://terratest.gruntwork.io/) to a Nitric project.
+
+## How Terratest works
+
+Terratest is designed to automate the entire process of testing your Terraform code with the following steps:
+
+- **Initialize**: Terratest will automatically run `terraform init` to initialize the Terraform working directory.
+- **Apply**: It will then run `terraform apply` to deploy the infrastructure as defined in your Terraform code.
+- **Assert**: The test script will then run assertions to check that the infrastructure was created as expected.
+- **Teardown**: Finally, it will run `terraform destroy` to tear down the infrastructure after the test completes.
+
+## What we'll be doing
+
+1. Create and set up your application.
+2. Deploying to AWS with a Terraform provider.
+3. Add and execute Terratest.
+
+## Create and set up your application.
+
+Our sample project creates a real-time communication service using [WebSockets](/websockets) and a [key-value store](/keyvalue) for connections.
+
+We intend to deploy to AWS and will use Terratest to ensure that the following:
+
+- **WebSocket API**: Confirm that the API Gateway WebSocket API is correctly configured for real-time communication.
+- **DynamoDB Table**: Verify that the key-value store for connections is created and operational.
+- **IAM Roles**: Ensure that permissions for interacting with AWS services are correctly set up.
+
+Let's begin by setting up the WebSocket application that we'll be testing.
+
+
+ You can find a more detailed set of instructions in this guide - [Building a
+ chat app in Go with WebSockets and Nitric ](/guides/go/realtime-messaging).
+ Once you've completed this guide, skip to [the next
+ step](#deploying-to-aws-with-a-terraform-provider)
+
+
+```bash
+nitric new websocket-app go-starter
+```
+
+Then you can delete all files/folders in the `services/` folder and add a file named `main.go` to your 'services/websockets' folder with the following code:
+
+```go title:services/websockets/main.go
+package main
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/nitrictech/go-sdk/nitric"
+ "github.com/nitrictech/go-sdk/nitric/keyvalue"
+ "github.com/nitrictech/go-sdk/nitric/websockets"
+)
+
+func main() {
+ // Create a WebSocket endpoint named "public".
+ ws := nitric.NewWebsocket("public")
+
+ // Initialize a KV store named "connections" with Get, Set, and Delete permissions.
+ connections := nitric.NewKv("connections").Allow(keyvalue.KvStoreGet, keyvalue.KvStoreSet, keyvalue.KvStoreDelete)
+
+ // Handle new WebSocket connections by storing the connection ID in the KV store.
+ ws.On(websockets.EventType_Connect, func(ctx *websockets.Ctx) {
+ err := connections.Set(context.Background(), ctx.Request.ConnectionID(), map[string]interface{}{
+ "connectionId": ctx.Request.ConnectionID(),
+ })
+ if err != nil {
+ fmt.Println("Error storing connection ID in KV store:", err)
+ return
+ }
+ })
+
+ ws.On(websockets.EventType_Disconnect, func(ctx *websockets.Ctx) {
+ err := connections.Delete(context.Background(), ctx.Request.ConnectionID())
+ if err != nil {
+ fmt.Println("Error deleting connection ID in KV store:", err)
+ return
+ }
+ })
+
+ ws.On(websockets.EventType_Message, func(ctx *websockets.Ctx) {
+ connectionStream, err := connections.Keys(context.Background())
+ if err != nil {
+ fmt.Println("Error retrieving connection keys from KV store:", err)
+ return
+ }
+
+ senderId := ctx.Request.ConnectionID()
+
+ for {
+ connectionId, err := connectionStream.Recv()
+ if err != nil {
+ break
+ }
+
+ if connectionId == senderId {
+ continue
+ }
+
+ message := fmt.Sprintf("%s: %s", senderId, ctx.Request.Message())
+ err = ws.Send(context.Background(), connectionId, []byte(message))
+ if err != nil {
+ fmt.Println("Error sending message to connection ID", connectionId, ":", err)
+ return
+ }
+ }
+ })
+
+ nitric.Run()
+}
+```
+
+## Deploying to AWS with a Terraform provider
+
+To deploy your application with Terraform you'll need to use Nitric's Terraform providers. You can learn more about using Nitric with Terraform [here](/providers/terraform).
+
+```
+nitric stack new dev aws-tf
+```
+
+Update this newly created stack file to include your target region:
+
+```yaml title:nitric.dev.yaml
+# The nitric provider to use
+provider: nitric/awstf@1.11.6
+
+# The target aws region to deploy to
+region: us-east-2
+```
+
+The Nitric Terraform providers are currently in preview, to enable them you'll need to enable beta-providers in your Nitric project. You can do this by adding the following to your project's `nitric.yaml` file:
+
+```yaml title:nitric.yaml
+preview:
+ - beta-providers
+```
+
+Once you've created your stack file, you can generate the Terraform code by running the following command:
+
+```
+nitric up
+```
+
+This will generate Terraform code which can deploy your application. The output will be in a folder named `cdktf.out` by default.
+
+## Add and execute Terratest
+
+Add the necessary dependencies for Terratest:
+
+```bash
+go get github.com/gruntwork-io/terratest/modules/terraform
+go get github.com/stretchr/testify/assert
+go get github.com/aws/aws-sdk-go/aws
+go get github.com/aws/aws-sdk-go/aws/session
+go get github.com/aws/aws-sdk-go/service/apigatewayv2
+go get github.com/aws/aws-sdk-go/service/dynamodb
+go get github.com/aws/aws-sdk-go/service/iam
+go get google.golang.org/genproto@latest
+```
+
+### Create the Test File
+
+Create a new file named `terraform_resources_test.go` in your project’s test directory:
+
+```bash
+mkdir -p test
+touch test/terraform_resources_test.go
+```
+
+Add the following code to `terraform_resources_test.go`:
+
+```go title:test/terraform_resources_test.go
+package test
+
+import (
+ "testing"
+ "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/aws-sdk-go/aws/session"
+ "github.com/aws/aws-sdk-go/service/apigatewayv2"
+ "github.com/aws/aws-sdk-go/service/dynamodb"
+ "github.com/aws/aws-sdk-go/service/iam"
+ "github.com/gruntwork-io/terratest/modules/terraform"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestTerraformResources(t *testing.T) {
+ // Set Terraform options, specifying the directory with your Terraform configuration
+ terraformOptions := &terraform.Options{
+ TerraformDir: "../cdktf.out/stacks/websocket-app-dev",
+ }
+
+ // Ensure resources are destroyed after test completion
+ defer terraform.Destroy(t, terraformOptions)
+
+ // Initialize and apply the Terraform configuration
+ terraform.InitAndApply(t, terraformOptions)
+
+ // Initialize AWS session for interacting with AWS services
+ sess := session.Must(session.NewSession(&aws.Config{Region: aws.String("us-east-2")}))
+
+ // Test DynamoDB table creation (key-value store)
+ dynamoClient := dynamodb.New(sess)
+ tableName := "connections" // Name of the DynamoDB table to check
+ _, err := dynamoClient.DescribeTable(&dynamodb.DescribeTableInput{
+ TableName: aws.String(tableName),
+ })
+ assert.NoError(t, err, "Expected DynamoDB table 'connections' to be created")
+
+ // Test IAM role creation
+ iamClient := iam.New(sess)
+ roleName := "websocket-app_websockets-main" // Name of the IAM role to check
+ _, err = iamClient.GetRole(&iam.GetRoleInput{
+ RoleName: aws.String(roleName),
+ })
+ assert.NoError(t, err, "Expected IAM role 'websocket-app_websockets-main' to be created")
+
+ // Test API gateway webSocket creation
+ apiClient := apigatewayv2.New(sess)
+ apiName := "public" // Name of the API Gateway WebSocket to check
+ result, err := apiClient.GetApis(&apigatewayv2.GetApisInput{})
+ assert.NoError(t, err)
+ found := false
+ for _, api := range result.Items {
+ if *api.Name == apiName {
+ found = true
+ break
+ }
+ }
+ assert.True(t, found, "Expected API Gateway WebSocket 'public' to be created")
+}
+```
+
+### Run the tests
+
+To run the tests, navigate to your project’s root directory and execute the Go test command:
+
+```bash
+go test -v ./test
+```
+
+This will:
+
+1. Deploy the infrastructure using Terraform.
+2. Validate the creation of the DynamoDB table, IAM role, and API Gateway WebSocket.
+3. Clean up the infrastructure after testing.
+
+The output should confirm the successful creation and validation of each resource.
diff --git a/public/images/guides/realtime-messaging/dashboard.png b/public/images/guides/realtime-messaging/dashboard.png
new file mode 100644
index 000000000..7a9da077c
Binary files /dev/null and b/public/images/guides/realtime-messaging/dashboard.png differ