TypeIDs are a draft standard for type-safe, globally unique identifiers based on the UUIDv7 standard. Their properties, particularly k-sortability, make them suitable primary identifiers for classic database systems like PostgreSQL. However, k-sortability may not always be desirable. For instance, you might require an identifier with high randomness entropy for security reasons. Additionally, in distributed database systems like CockroachDB, having a k-sortable primary key can lead to hotspots and performance issues.
While this package draws inspiration from the original typeid-go package (github.com/jetify-com/typeid-go), it provides multiple ID types:
typeid.Sortableis based on UUIDv71 and is k-sortable. Its implementation adheres to the draft standard. The suffix part is encoded in lowercase crockford base32.typeid.Randomis also based on UUIDv42 and is completely random. Unliketypeid.Sortable, the suffix part is encoded in uppercase crockford base32.
Please refer to the respective type documentation for more details.
go get github.com/sumup/typeidTo create a new ID type, define a prefix type that implements the typeid.Prefix interface. Then, define a TypeAlias for your ID type to typeid.Random or typeid.Sortable with your prefix type as generic argument.
Example:
import "github.com/sumup/typeid"
type UserPrefix struct{}
func (UserPrefix) Prefix() string {
return "user"
}
type UserID = typeid.Sortable[UserPrefix]
userID, err := typeid.New[UserID]()
if err != nil {
fmt.Println("create user id:", err)
}
fmt.Println(userID) // --> user_01hf98sp99fs2b4qf2jm11hse4ID types in this package can be used with database/sql and github.com/jackc/pgx.
When using the standard library SQL, IDs will be stored as their string representation and can be scanned and valued accordingly. When using pgx, both TEXT and UUID columns can be used directly. However, note that the type information is lost when using UUID columns, unless you take additional steps at the database layer. Be mindful of your identifier semantics, especially in complex JOIN queries.
If using pgx with PostgreSQL, you can generate UUIDv4 (for usage with typeid.Random) as the default value for your primary key:
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID(),
...
);For k-sortable TypeIDs (typeid.Sortable, using UUIDv7) you will have to generate the UUID in your application unless you are running PostgreSQL 18 or new where both are supported:
-- With random TypeID
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuidv4(),
...
);
-- With sortable TypeID
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuidv7(),
...
);TypeIDs work seamlessly with sqlc by using column overrides in your sqlc.yaml configuration:
version: "2"
sql:
- schema: "schema.sql"
queries: "queries"
engine: postgresql
gen:
go:
package: postgres
out: postgres
sql_package: pgx/v5
overrides:
- column: users.id
go_type:
import: github.com/yourorg/yourproject/internal/domain
type: "UserID"With this configuration, sqlc will generate Go code that uses your TypeID type directly:
package domain
import "github.com/sumup/typeid"
type UserPrefix struct{}
func (UserPrefix) Prefix() string {
return "user"
}
type UserID = typeid.Sortable[UserPrefix]
type User struct {
ID UserID
Name string
// ... other fields
}You can then use your TypeID types directly in your queries:
-- name: CreateUser :one
INSERT INTO users (
id,
name
) VALUES (
@id,
@name
) RETURNING *;
-- name: GetUser :one
SELECT * FROM users
WHERE id = @id;And call them from Go:
userID := typeid.Must(typeid.New[domain.UserID]())
user, err := queries.CreateUser(ctx, postgres.CreateUserParams{
ID: userID,
Name: "Karl",
})TypeIDs can be used with oapi-codegen to generate type-safe API clients and servers. Use the x-go-type and x-go-type-import extensions in your OpenAPI specification:
paths:
/users/{user_id}:
parameters:
- in: path
name: user_id
description: The ID of the uesr to retrieve.
required: true
schema:
type: string
example: user_01hf98sp99fs2b4qf2jm11hse4
x-go-type: "domain.UserID"
x-go-type-import:
path: github.com/yourorg/yourproject/internal/domain
components:
schemas:
User:
type: object
properties:
id:
type: string
description: Unique identifier of the user.
example: user_01hf98sp99fs2b4qf2jm11hse4
x-go-type: "domain.UserID"
x-go-type-import:
path: github.com/yourorg/yourproject/internal/domain
name:
type: stringThe generated code will use your TypeID types:
type User struct {
Id domain.UserID `json:"id"`
Name string `json:"name"`
}TypeIDs implement encoding.TextMarshaler and encoding.TextUnmarshaler, so they work with JSON encoding/decoding in generated API code without any additional configuration.
Based on the go implementation of typeid found at: https://github.com/jetify-com/typeid-go by Jetify. Modifications made available under the same license as the original.