The official Clerk Go client library for accessing the Clerk Backend API.
Check out the upgrade guide.
- Go 1.19 or later.
If you are using Go Modules and have a go.mod
file in your project's root, you can import clerk-sdk-go directly.
import (
"github.com/clerk/clerk-sdk-go/v2"
)
Alternatively, you can go get
the package explicitly.
go get -u github.com/clerk/clerk-sdk-go/v2
For details on how to use this module, see the Go Documentation.
The library has a resource-based structure which follows the way the Clerk Backend API resources are organized. Each API supports specific operations, like Create or List. While operations for each resource vary, a similar pattern is applied throughout the library.
In order to start using API operations the library needs to be configured with your Clerk API secret key. Depending on your use case, there's two ways to use the library; with or without a client.
For most use cases, the API without a client is a better choice.
On the other hand, if you need to set up multiple Clerk API keys, using clients for API operations provides more flexibility.
Let's see both approaches in detail.
If you only use one API key, you can import the packages required for the APIs you need to interact with and call functions for API operations.
import (
"github.com/clerk/clerk-sdk-go/v2"
"github.com/clerk/clerk-sdk-go/v2/$resource$"
)
// Each operation requires a context.Context as the first argument.
ctx := context.Background()
// Set the API key
clerk.SetKey("sk_live_XXX")
// Create
resource, err := $resource$.Create(ctx, &$resource$.CreateParams{})
// Get
resource, err := $resource$.Get(ctx, id)
// Update
resource, err := $resource$.Update(ctx, id, &$resource$.UpdateParams{})
// Delete
resource, err := $resource$.Delete(ctx, id)
// List
list, err := $resource$.List(ctx, &$resource$.ListParams{})
for _, resource := range list.$Resource$s {
// do something with the resource
}
If you're dealing with multiple keys, it is recommended to use a client based approach. The API packages for each resource export a Client, which supports all the API's operations. This way you can create as many clients as needed, each with their own API key.
import (
"github.com/clerk/clerk-sdk-go/v2"
"github.com/clerk/clerk-sdk-go/v2/$resource$"
)
// Each operation requires a context.Context as the first argument.
ctx := context.Background()
// Initialize a client with an API key
config := &clerk.ClientConfig{}
config.Key = "sk_live_XXX"
client := $resource$.NewClient(config)
// Create
resource, err := client.Create(ctx, &$resource$.CreateParams{})
// Get
resource, err := client.Get(ctx, id)
// Update
resource, err := client.Update(ctx, id, &$resource$.UpdateParams{})
// Delete
resource, err := client.Delete(ctx, id)
// List
list, err := client.List(ctx, &$resource$.ListParams{})
for _, resource := range list.$Resource$s {
// do something with the resource
}
Here's an example of how the above operations would look like for specific APIs.
import (
"github.com/clerk/clerk-sdk-go/v2"
"github.com/clerk/clerk-sdk-go/v2/organization"
"github.com/clerk/clerk-sdk-go/v2/organizationmembership"
"github.com/clerk/clerk-sdk-go/v2/user"
)
func main() {
// Each operation requires a context.Context as the first argument.
ctx := context.Background()
// Set the API key
clerk.SetKey("sk_live_XXX")
// Create an organization
org, err := organization.Create(ctx, &organization.CreateParams{
Name: clerk.String("Clerk Inc"),
})
// Update the organization
org, err = organization.Update(ctx, org.ID, &organization.UpdateParams{
Slug: clerk.String("clerk"),
})
// List organization memberships
listParams := organizationmembership.ListParams{}
listParams.Limit = clerk.Int64(10)
memberships, err := organizationmembership.List(ctx, params)
if memberships.TotalCount < 0 {
return
}
membership := memberships[0]
// Get a user
usr, err := user.Get(ctx, membership.UserID)
}
Each resource that is returned by an API operation has a Response
field which
contains information about the response that was sent from the Clerk Backend API.
The Response
contains fields like the the raw HTTP response's headers,
the status and the raw response body. See the APIResponse
documentation for available fields and methods.
dmn, err := domain.Create(context.Background(), &domain.CreateParams{})
if !dmn.Response.Success() {
dmn.Response.TraceID
}
For cases where an API operation returns an error, the library will try to return an APIErrorResponse
.
The APIErrorResponse
type provides information such as the HTTP status code of the response, a list of errors
and a trace ID that can be used for debugging.
The APIErrorResponse
is an APIResource
. You can access the API response for errors as well.
See the APIErrorResponse documentation for available fields and methods.
_, err := user.List(context.Background(), &user.ListParams{})
if apiErr, ok := err.(*clerk.APIErrorResponse); ok {
apiErr.TraceID
apiErr.Error()
apiErr.Response.RawJSON
}
The library provides two functions that can be used for adding authentication with Clerk to HTTP handlers.
Both middleware functions support header based authentication with a bearer token. The token is parsed, verified and its claims are extracted as SessionClaims.
The claims will then be made available in the http.Request.Context
for the next handler in the chain. The library
provides a helper for accessing the claims from the context, SessionClaimsFromContext.
The two middleware functions are WithHeaderAuthorization
and RequireHeaderAuthorization.
Their difference is that the RequireHeaderAuthorization
calls WithHeaderAuthorization
under the hood, but responds
with HTTP 403 Forbidden if it fails to detect valid session claims.
Let's see an example of how the middleware can be used.
import (
"fmt"
"net/http"
"github.com/clerk/clerk-sdk-go/v2"
clerkhttp "github.com/clerk/clerk-sdk-go/v2/http"
)
func main() {
clerk.SetKey("sk_live_XXX")
mux := http.NewServeMux()
mux.HandleFunc("/", publicRoute)
protectedHandler := http.HandlerFunc(protectedRoute)
mux.Handle(
"/protected",
clerkhttp.WithHeaderAuthorization()(protectedHandler),
)
http.ListenAndServe(":3000", mux)
}
func publicRoute(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"access": "public"}`))
}
func protectedRoute(w http.ResponseWriter, r *http.Request) {
claims, ok := clerk.SessionClaimsFromContext(r.Context())
if !ok {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"access": "unauthorized"}`))
return
}
fmt.Fprintf(w, `{"user_id": "%s"}`, claims.Subject)
}
Both WithHeaderAuthorization
and RequireHeaderAuthorization
can be
customized. They accept various options as functional arguments.
For a comprehensive list of available options check the AuthorizationParams documentation.
There are various ways to mock the library in your test suite.
If you're using the library without instantiating clients for APIs, you can
set the package's Backend
with a custom configuration.
- Stub out the HTTP client's transport
func TestWithCustomTransport(t *testing.T) {
clerk.SetBackend(clerk.NewBackend(&clerk.BackendConfig{
HTTPClient: &http.Client{
Transport: mockRoundTripper,
},
}))
}
type mockRoundTripper struct {}
// Implement the http.RoundTripper interface.
func (r *mockRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
// Construct and return the http.Response.
}
- Use a httptest.Server
Similar to the custom http.Transport approach, you can use the net/http/httptest package's utilities and provide the http.Client to the package's Backend.
func TestWithMockServer(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Write the response.
}))
defer ts.Close()
clerk.SetBackend(clerk.NewBackend(&clerk.BackendConfig{
HTTPClient: ts.Client(),
URL: &ts.URL,
}))
}
- Implement your own Backend
You can implement your own Backend and set it as the package's default Backend.
func TestWithCustomBackend(t *testing.T) {
clerk.SetBackend(&customBackend{})
}
type customBackend struct {}
// Implement the Backend interface
func (b *customBackend) Call(ctx context.Context, r *clerk.APIRequest, reader clerk.ResponseReader) error {
// Construct a clerk.APIResponse and use the reader's Read method.
reader.Read(&clerk.APIResponse{})
}
If you're already using the library by instantiating clients for API operations, or you need to ensure your test suite can safely run in parallel, you can simply pass a custom http.Client to your clients.
- Stub out the HTTP client's transport
func TestWithCustomTransport(t *testing.T) {
config := &clerk.ClientConfig{}
config.HTTPClient = &http.Client{
Transport: mockRoundTripper,
}
client := user.NewClient(config)
}
type mockRoundTripper struct {}
// Implement the http.RoundTripper interface.
func (r *mockRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
// Construct and return the http.Response.
}
- Use a httptest.Server
Similar to the custom http.Transport approach, you can use the net/http/httptest package's utilities and provide the http.Client to the API client.
func TestWithMockServer(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Write the response.
}))
defer ts.Close()
config := &clerk.ClientConfig{}
config.HTTPClient = ts.Client()
config.URL = &ts.URL
client := user.NewClient(config)
}
- Implement your own Backend
You can implement your own Backend and set it as the API client's Backend.
func TestWithCustomBackend(t *testing.T) {
client := user.NewClient(&clerk.ClientConfig{})
client.Backend = &customBackend{}
}
type customBackend struct {}
// Implement the Backend interface
func (b *customBackend) Call(ctx context.Context, r *clerk.APIRequest, reader *clerk.ResponseReader) error {
// Construct a clerk.APIResponse and use the reader's Read method.
reader.Read(&clerk.APIResponse{})
}
Contributions are welcome. If you submit a pull request please keep in mind that
- Code must be
go fmt
compliant. - All packages, types and functions should be documented.
- Ensure that
go test ./...
succeeds. Ideally, your pull request should include tests. - If your pull request introduces a new API or API operation, run
go generate ./...
to generate the necessary API functions.
This SDK is licensed under the MIT license found in the LICENSE file.