westack-go is a modular Go framework designed to simplify the process of building scalable and extensible APIs. It provides utilities for managing data models, routing, and middleware, along with powerful integrations for Swagger documentation, CLI tools, and more.
- Model-Driven Architecture: Define data models with ease and generate APIs automatically.
- Extensible Datasources: Support for in-memory and MongoDB datasources out of the box.
- Role Management: Built-in role-based access control (RBAC) support.
- CLI Utilities: Command-line tools for common development tasks.
- Go (version 1.21 or higher)
- MongoDB (if using MongoDB as a datasource)
- Basic knowledge of Go programming
Follow these steps to quickly set up and run a simple API:
-
Create the project directory:
Create a directory for your project and initialize a Go module:
mkdir myproject && cd myproject go mod init myproject
This sets up the directory and initializes Go module management for your project.
-
Install the westack-go CLI:
Install the CLI tool to simplify project setup and management:
go install github.com/fredyk/westack-go/v2@latest
The CLI provides commands like
initandgeneratefor rapid development. -
Initialize the project:
Use the CLI to set up the project structure:
westack-go init .This creates the basic structure, including configuration files and directories for models and controllers.
-
Define a model:
Create a
models/note.jsonfile:{ "name": "Note", "base": "PersistedModel", "properties": { "title": { "type": "string", "required": true }, "content": { "type": "string" } }, "casbin": { "policies": [ "$authenticated,*,*,allow", "$everyone,*,read,allow", "$owner,*,__get__footer,allow" ] } } -
Generate the model:
westack-go generate
-
Create the main application file:
Create a
main.gofile with the following content:package main import ( "log" "github.com/fredyk/westack-go/v2/westack" "myproject/models" ) func main() { app := westack.New() app.Boot(westack.BootOptions{ RegisterControllers: models.RegisterControllers, }) log.Fatal(app.Start()) }
-
Run the server:
go run main.go
-
Test the API:
Access the Swagger UI at
http://localhost:3000/swaggerto test your endpoints.
westack-go is built around the following core components:
- Models: Define the structure of your data and generate APIs automatically.
- Datasources: Abstract the details of data storage, supporting MongoDB and in-memory stores.
- Routing: Manage API endpoints and middleware.
- Controllers: Centralize business logic.
- CLI Utilities: Simplify repetitive tasks like generating models and controllers.
Models are the backbone of westack-go, defined in JSON files under the models/ directory. These JSON files specify attributes, relationships, and access policies using Casbin. From these definitions, westack-go generates Go struct files with the westack-go generate command.
Example of a JSON Model:
{
"name": "Note",
"base": "PersistedModel",
"properties": {
"title": {
"title": {
"type": "string",
"required": true
},
"content": {
"type": "string"
}
},
"casbin": {
"policies": [
"$authenticated,*,*,allow",
"$everyone,*,read,allow",
"$owner,*,__get__footer,allow"
]
}
}When you run:
westack-go generateThis generates Note in models/note.go (if it does not already exist). The Go file can then be extended for additional functionality without affecting the original JSON definitions.
This dual-layer approach allows developers to:
- Keep JSON files as the source of truth for relationships and Casbin policies.
- Extend models in Go for advanced functionality.
Note: In the future, the JSON files may be deprecated, and direct Go struct definitions might become the standard.
By default, westack-go generates the following standard CRUD routes for the Note model:
POST /notes: Create a new noteGET /notes: Retrieve all notesGET /notes/{id}: Retrieve a specific note by IDPATCH /notes/{id}: Partially update fields of a specific noteDELETE /notes/{id}: Delete a specific note by ID
You can relate models using the relations property in the JSON definition. For example, to relate Footer to Note (and define that Note has one Footer):
Create or update models/footer.json:
{
"name": "Footer",
"base": "PersistedModel",
"properties": {
"content": {
"type": "string",
"required": true
}
},
"relations": {
"note": {
"type": "belongsTo",
"model": "Note",
"foreignKey": "noteId"
"note": {
"type": "belongsTo",
"model": "Note",
"foreignKey": "noteId"
}
},
"casbin": {
"policies": [
"$authenticated,*,*,allow",
"$everyone,*,read,allow",
"$owner,*,__get__note,allow"
]
},
"casbin": {
"policies": [
"$authenticated,*,*,allow",
"$everyone,*,read,allow",
"$owner,*,__get__note,allow"
]
}
}Create or update models/note.json:
Create or update models/note.json:
{
"name": "Note",
"base": "PersistedModel",
"properties": {
"title": {
"type": "string",
"required": true
},
"content": {
"type": "string"
"content": {
"type": "string"
}
},
"relations": {
"footer": {
"type": "hasOne",
"model": "Footer",
"foreignKey": "noteId"
}
},
"casbin": {
"policies": [
"$authenticated,*,*,allow",
"$everyone,*,read,allow",
"$owner,*,__get__footer,allow"
"$authenticated,*,*,allow",
"$everyone,*,read,allow",
"$owner,*,__get__footer,allow"
]
}
}Run the following command to regenerate the models:
westack-go generateThis will establish the relationship where Footer belongs to Note and Note has one Footer, allowing CRUD operations to respect the relationship automatically.
-
Define the Model Create a JSON file in the
models/directory and define your data structure. -
Generate the Go Struct Run the following command to generate the corresponding Go file:
westack-go generate
-
Extend the Model If needed, extend the generated Go struct file for additional functionality.
-
Adding Custom Logic Use
BindRemoteOperationWithOptionsto add new functionality or routes for an existing model.
Example:
package boot
import (
"log"
"github.com/fredyk/westack-go/v2/model"
"github.com/fredyk/westack-go/v2/westack"
)
func SetupServer(app *westack.WeStack) {
NoteModel, err := app.FindModel("Note")
if err != nil {
log.Fatalf("Error finding model: %v", err)
}
model.BindRemoteOperationWithOptions(NoteModel, CustomHandler, model.RemoteOptions().
WithName("customEndpoint").
WithPath("/notes/custom").
WithContentType("application/json"))
}
type CustomInput struct {
Field1 string `json:"field1"`
Field2 int `json:"field2"`
}
type CustomOutput struct {
Message string `json:"message"`
Status string `json:"status"`
}
func CustomHandler(input CustomInput) (CustomOutput, error) {
return CustomOutput{
Message: "This is a custom endpoint.",
Status: "success",
}, nil
}westack-go automatically generates Swagger documentation for your APIs. The Swagger UI is available at:
/swagger: Interactive API documentation./swagger/doc.json: The OpenAPI specification in JSON format.
Filters in westack-go allow developers to:
- Query, sort, paginate, and limit data retrieved from models.
- Build flexible APIs that support custom data slices without hardcoding query logic.
westack-go automatically manages the following fields:
created: Added when a record is created.modified: Updated whenever a record is modified.
Filters can be applied to standard CRUD endpoints, such as GET /notes, to refine the data returned.
Filters are specified as JSON objects within the filter query parameter. Spaces within filter values should be replaced with the + character to ensure proper parsing by the API.
You can filter records based on specific field values using the following format:
GET /notes?filter={"where":{"field":"value"}}Example:
GET /notes?filter={"where":{"title":"Meeting"}}This retrieves all Note records where the title field equals Meeting.
For more complex filtering, you can use comparison operators:
$gt(greater than)$gte(greater than or equal to)$lt(less than)$lte(less than or equal to)$ne(not equal to)$in(in array)$regex(regular expression)
Example:
GET /notes?filter={"where":{"content":{"$regex":".*important.*"}}}This retrieves all Note records where the content field contains the substring "important".
You can sort records by one or more fields using the order parameter:
GET /notes?filter={"order":["field+ASC"]}Example:
GET /notes?filter={"order":["title+ASC"]}This retrieves all Note records sorted by the title field in ascending order.
To limit the number of results returned and implement pagination, use the limit and skip parameters:
limit: Specifies the maximum number of records to return.skip: Skips the specified number of records before returning results.
Example:
GET /notes?filter={"limit":10,"skip":20}This retrieves 10 Note records starting from the 21st record.
To retrieve only specific fields from a record, use the fields parameter:
GET /notes?filter={"fields":{"field1":true,"field2":false}}Example:
GET /notes?filter={"fields":{"title":true,"content":false}}This retrieves only the title field and excludes the content field for all Note records.
Filters can be combined to build complex queries:
GET /notes?filter={"where":{"title":"Meeting"},"order":["title+DESC"],"limit":5}This retrieves up to 5 Note records where the title is "Meeting", sorted in descending order by title.
Retrieve all notes where content contains "urgent":
GET /notes?filter={"where":{"content":{"$regex":".*urgent.*"}}}Retrieve the first 10 notes sorted by created in descending order:
GET /notes?filter={"order":["created+DESC"],"limit":10}Skip the first 5 notes and retrieve the next 15 notes:
GET /notes?filter={"limit":15,"skip":5}Retrieve all notes where title is "Meeting" and content does not contain "canceled":
GET /notes?filter={"where":{"title":"Meeting","content":{"$not":{"$regex":".*canceled.*"}}}}To include related models, use the include parameter:
GET /notes?filter={"include":[{"relation":"footer"}]}This retrieves Note records along with their related Footer records.
You can include related models and apply additional filters simultaneously. For example:
GET /notes?filter={"where":{"title":"Meeting"},"include":[{"relation":"footer"}]}This retrieves Note records where the title is "Meeting" and includes the related Footer records.
Performance: Complex filters might impact query performance, especially with large datasets.
Filters are a powerful feature of westack-go, making it easy to build flexible, queryable APIs. For further customization or troubleshooting, consult the source code or westack-go examples.
-
v2.0.1-alpha
- Now the DELETE /:id endpoint returns a wst.DeleteResult schema object instead of an empty response
- Now the GET /count endpoint returns a wst.CountResult schema object instead of a root integer
-
v1.6.14
-
v1.6.0
-
v1.6.0
- Added parameter
strictSingleRelatedDocumentCheckin config.json, defaults totruein new projects, andfalsein existing ones. "hasOne"and"belongsTo"relations are now checked after fetching documents from Mongo. IfstrictSingleRelatedDocumentCheckistrueand the relation returns more than 1 document, an error is thrown. Otherwise, only the first document is used and a warning is logged.- Breaking changes:
model.Build()requires now parametersameLevelCache *buildCacheto be passed in. Can be generated withmodel.NewBuildCache()model.Build()returns nowerroras second value, in addition to the instance. So it is nowfunc (loadedModel *Model) Build(data wst.M, sameLevelCache *buildCache, baseContext *EventContext) (Instance, error)
- Added parameter
strictSingleRelatedDocumentCheckin config.json, defaults totruein new projects, andfalsein existing ones. "hasOne"and"belongsTo"relations are now checked after fetching documents from Mongo. IfstrictSingleRelatedDocumentCheckistrueand the relation returns more than 1 document, an error is thrown. Otherwise, only the first document is used and a warning is logged.- Breaking changes:
model.Build()requires now parametersameLevelCache *buildCacheto be passed in. Can be generated withmodel.NewBuildCache()model.Build()returns nowerroras second value, in addition to the instance. So it is nowfunc (loadedModel *Model) Build(data wst.M, sameLevelCache *buildCache, baseContext *EventContext) (Instance, error)
- Added parameter
-
v1.5.48
-
v1.5.48
- Breaking change: environment variables
WST_ADMIN_USERNAMEandWST_ADMIN_PWDare required to start the server - Breaking change: environment variables
WST_ADMIN_USERNAMEandWST_ADMIN_PWDare required to start the server
- Breaking change: environment variables
Write to westack.team@gmail.com if you want to contribute to the project: D
You are also welcome on our official Discord
And of course... create as many pull requests as you want!