Building a URL shortener in Go with Nitric
What we'll be doing
- Use Nitric to create a simple URL shortener service.
- Expose an HTTP endpoint for shortening URLs.
- Store shortened URLs in a Key-Value (KV) store.
- Redirect users to the original URL when accessing the short code.
- Run locally for testing.
- Deploy to AWS.
Prerequisites
- Go
- The Nitric CLI
- An AWS account (optional)
Getting started
We'll start by creating a new project for our WebSocket application. The finished source can be found here.
nitric new url-shortener go-starter
Next, open the project in your preferred editor:
cd url-shortener
Make sure all dependencies are resolved:
go mod tidy
The scaffolded project should have the following structure:
+--services/| +-- hello/| +-- main.go+--nitric.yaml+--go.mod+--go.sum+--golang.dockerfile+--.gitignore+--README.md
You can test the project to ensure everything is working as expected:
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.
Resource Initialization
We'll start by creating a resources
package that defines and initializes the API and KV store from the Nitric SDK. At this point, we won't request any permissions like get
or set
for the KV store. We'll do that in the individual services to ensure least privilege provisioning.
package resourcesimport ("sync""github.com/nitrictech/go-sdk/nitric""github.com/nitrictech/go-sdk/nitric/apis""github.com/nitrictech/go-sdk/nitric/keyvalue")type Resource struct {MainApi apis.ApiUrlKvStore keyvalue.KvStore}var (resource *ResourceresourceOnce sync.Once)func Get() *Resource {resourceOnce.Do(func() {mainApi := nitric.NewApi("main")resource = &Resource{MainApi: mainApi,UrlKvStore: nitric.NewKv("urls"),}})return resource}
Building the URL Shortener Application
Create a new folder called shortener
within the services
directory. Inside this folder, add a file named main.go
, and include the following code:
package mainimport ("math/rand""github.com/nitrictech/go-sdk/nitric""github.com/nitrictech/go-sdk/nitric/apis""github.com/nitrictech/go-sdk/nitric/keyvalue""github.com/nitrictech/templates/go-starter/resources")func main() {// Initialize the resources defined in resources.gourlKvStore := resources.Get().UrlKvStore.Allow(keyvalue.KvStoreSet, keyvalue.KvStoreGet)// POST /shorten - Shorten a given URLresources.Get().MainApi.Post("/shorten", func(ctx *apis.Ctx) {// TODO: implement app logic})// GET /:code - Redirect to the original URL associated with the short coderesources.Get().MainApi.Get("/:code", func(ctx *apis.Ctx) {// TODO: implement app logic})nitric.Run()}
Here we're creating placeholders for our API routes and initializing our KV store with permissions so we can GET
and SET
values.
Next, let's add a helper function to handle URL shortening and an inline struct to hold our URL data.
func generateShortCode() string {s := ""for i := 0; i < 6; i++ {s += string(rand.Intn(26) + 97) // Generate a lowercase letter}return s}var shortenData struct {Url string `json:"url"`}
Let's implement our shorten route:
// POST /shorten - Shorten a given URLresources.Get().MainApi.Post("/shorten", func(ctx *apis.Ctx) {err := json.Unmarshal(ctx.Request.Data(), &shortenData)if err != nil || strings.TrimSpace(shortenData.Url) == "" {ctx.Response.Status = http.StatusBadRequestctx.Response.Body = []byte("Invalid or missing URL")return}shortCode := generateShortCode()// Store the mapping of short code -> original URLerr = urlKvStore.Set(context.Background(), shortCode, map[string]interface{}{"url": shortenData.Url,})if err != nil {ctx.Response.Status = 500ctx.Response.Body = []byte("Error shortening URL")return}// Extract the origin from headers (for demonstration), then return the short URLorigin := ""if val, ok := ctx.Request.Headers()["X-Forwarded-For"]; ok {origin = val[0]} else if val, ok := ctx.Request.Headers()["x-forwarded-for"]; ok {origin = val[0]}ctx.Response.Body = []byte(fmt.Sprintf("%s/%s", origin, shortCode))})
Finally, we'll add a redirect handler to send users to the long URL when they use the short code:
// GET /:code - Redirect to the original URL associated with the short coderesources.Get().MainApi.Get("/:code", func(ctx *apis.Ctx) {code := ctx.Request.PathParams()["code"]data, err := urlKvStore.Get(context.Background(), code)if err == nil {ctx.Response.Headers["Location"] = []string{data["url"].(string)}ctx.Response.Status = 301} else {fmt.Println("Error getting URL: ", err)ctx.Response.Status = 404}})
Running locally
Ensure all dependencies are resolved and start the project:
go mod tidynitric start
Nitric will run the service locally. You can test the endpoints using tools like curl
or Postman. For example, to shorten a URL:
curl -X POST localhost:4001/shorten -d '{"url":"https://example.com"}' -H "Content-Type: application/json"
You should receive a response containing the short code which you can use in a browser to test the redirect:
http://localhost:4001/{shortcode}
Deploy to the Cloud
When you're ready to deploy this to the cloud, Nitric makes it straightforward. In this example, we’ll deploy to AWS. First, set up your credentials and configuration for the nitric/aws provider.
Create a Stack
Create a dev
stack that uses the aws
provider:
nitric stack new dev aws
This command creates a nitric.dev.yaml
file defining your deployment target. Edit it to configure your preferred AWS region (e.g., us-east-1
).
Deploying
Deploy the stack to the cloud:
nitric up
Once deployment is complete, you’ll have live endpoints accessible via AWS infrastructure. Test the endpoints using the new domain provided after deployment.
To remove deployed resources:
nitric down
Summary
In this guide, we’ve built a simple URL shortening service using Go and Nitric. We’ve shown how to:
- Set up an API to shorten URLs and redirect users.
- Use a Key-Value store to persist short code mappings.
- Run and test the application locally before deploying it to AWS.