Using the Gin Framework in Go: Implementing CORS Effectively
Fast APIs in Go with Gin's framework is all well and good, but let's also handle CORS like a pro. Get started with the right config in this practical guide.
Let’s talk about CORS and Gin. If you’re here it’s likely because you found the post on a search or because you’re subscribed (thank you!), and to be clear, we’re not talking about the Gin that makes your Friday night a little… blurrier, We're talking about the blazing-fast, ultra-efficient web framework for Go that'll have you building APIs faster than you can say "Hello, World!". And because we're all about being good citizens of the internet, we'll also tackle the often-dreaded, but essential, topic of CORS.
Gin & Tonic (…the Web Kind!)
Why Gin, you ask? Well, here’s the lowdown:
Speed Demon: Gin is built for performance. It’s fast, like, really fast. We’re talking insane speed here.
Simplicity is King: Gin keeps things simple and elegant. It's not trying to be a Swiss Army knife; it focuses on doing one thing well – handling web requests.
Middleware Magic: Gin is all about middleware. These are like tiny little helpers that can perform tasks before or after your route handlers.
Great Community: You're not alone! Gin has a pretty helpful community and if you get stuck, you'll find a wealth of resources ready to lend a hand, like this post (hopefully!).
Here’s the link to the official Gin page https://gin-gonic.com/
Alright, let’s get down to business!
Setting Up Your Gin-fueled API
First things first, you’ll need to install Gin (if you haven't already):
go get github.com/gin-gonic/gin
Now, let's create a simple API that returns a witty greeting:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello there, Gin-ius!",
})
})
router.Run(":8080") // Listen on port 8080
}
Not too shabby, right? This code creates a Gin router, defines a route for the root path (/
), and then serves a JSON response. Fire up this code and point your browser or curl command at http://localhost:8080. You should see our witty greeting in all its JSON glory.
CORS: The API Bouncer
Now, let’s talk about CORS (Cross-Origin Resource Sharing). Imagine your API is a super exclusive club. CORS is the bouncer at the door, making sure only the authorised people get in. Without it, browsers would be like overprotective parents, refusing to let your frontend make requests to your backend if they’re not from the same origin. Note, that this doesn’t actually replace authorisation or authentication, that’s a topic for another day.
Why the Fuss About Origins?
Browsers have this thing called the "Same-Origin Policy," which is a security mechanism that prevents malicious scripts on one website from accessing resources from another. It's a good thing… for the most part. But it can get in the way when your frontend is at http://myfrontend.com and your API is at http://myapi.com.
How CORS Works in Gin
CORS basically boils down to your server (Gin in our case) telling the browser, "Hey, it's cool, this request is okay." It does this by sending specific headers in the response.
Let's add CORS support to our Gin app.
Here’s a middleware function we can use which will be called on all the routes we need it to be.
func CorsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "https://myfrontend.com")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, Authorization")
c.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PATCH, DELETE, PUT")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
Let's break down this function:
We set the header
Access-Control-Allow-Origin
to our frontend URL. This says tells the request what origin our server accepts to make sure from a browser perspective that this origin is allowed.We set another header that says the allow-credentials header is allowed.
The third header refers to what other headers the browser is allowed to send through to this server.
The fourth piece we set which HTTP verbs we want to allow on our server.
The last part of the function aborts the request chain and simply returns a 204 or “No Content” HTTP response back to the client indicating a successful options call with those headers in the response.
If it’s not an options call, then we allow the next operation in the chain to continue.
Important CORS Considerations
AllowAllOrigins: While
Set("Access-Control-Allow-Origin", "*")
seems convenient, it's generally not recommended for production as it can open your API to security risks. It's only really helpful for development.Preflight Requests: Browsers often send a "preflight" request with the OPTIONS method before making the actual request to check if CORS is configured correctly. This middleware handles this automatically for us.
Credentials: If you need to send cookies or use authorization headers, you might need to set
AllowCredentials
to true in your setup but you can simply omit it if don’t.
Implementing the middleware into the router
Now that we’ve got the middleware function it’s time to bring it into our router. But where to put it?
There’s actually quite a few options, but you need to be careful where you put it. I was tripped over by this a few times until I got it right when I was building RocketFlag and so I wanted to share the wisdom.
In my case I needed some API endpoints to be open, but others needed to be protected by CORS (and other stuff).
Let’s reconsider this code (snipped for brevity)
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello there, Gin-ius!",
})
})
router.Run(":8080")
If you wanted to inject CORS here and protect all your endpoints, then it’s easy. Just jam it in after the router is defined, before the routes. Eg:
router := gin.Default()
router.Use(CorsMiddleware()) // The function we created earlier.
// rest of the code
But what about if you want some routes protected and some completely open. In my case, with RocketFlag, it’s possible some browsers will be accessing the API from a different origin, in fact there’s infinite possible domains that requests might come from, as a feature flagging service.
Here’s a sample of what I did:
router := gin.Default()
router.GET("/hello", handlerFunc())
// Other public routes here.
router.Use(CorsMiddleware()) // Add the middleware here
router.GET("/helloFromSpecificOrigin", handlerFunc())
// This and other routes defined after the .Use function protected.
Whilst not a fully worked example, this should give you the sample of what I needed to do in order to support CORS on a web app with cross origin needs!
Worth noting:
You can also assign middleware to router groups too, for specific groups of routes that you want the middleware to apply to. For example:
// Protected API Routes
api := r.Group("/api", server.Authenticate())
{
r.GET(/protected, handler())
}
This config allows you apply the server.Authenticate()
middleware to all your routes in that group, such that /api/protected
will be a protected route on your server by that middleware, without the route explicitly needing to have it defined. You could do a similar thing with CORS in this instance.
Final Thoughts
And there you have it, friends! You’ve built a simple API using the amazing Gin framework and learned how to tame the wild beast that is CORS. Now go forth and build incredible things, secure in the knowledge that your API bouncers are doing their job, and your Gin server is running fast.
Remember, when the world gives you lemons, make lemonade…or in our case, fast and secure APIs using Gin. Happy coding!