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
init
andgenerate
for 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.json
file:{ "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.go
file 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/swagger
to 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 generate
This 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 generate
This 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
BindRemoteOperationWithOptions
to 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
strictSingleRelatedDocumentCheck
in config.json, defaults totrue
in new projects, andfalse
in existing ones. "hasOne"
and"belongsTo"
relations are now checked after fetching documents from Mongo. IfstrictSingleRelatedDocumentCheck
istrue
and 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 *buildCache
to be passed in. Can be generated withmodel.NewBuildCache()
model.Build()
returns nowerror
as 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
strictSingleRelatedDocumentCheck
in config.json, defaults totrue
in new projects, andfalse
in existing ones. "hasOne"
and"belongsTo"
relations are now checked after fetching documents from Mongo. IfstrictSingleRelatedDocumentCheck
istrue
and 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 *buildCache
to be passed in. Can be generated withmodel.NewBuildCache()
model.Build()
returns nowerror
as 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_USERNAME
andWST_ADMIN_PWD
are required to start the server - Breaking change: environment variables
WST_ADMIN_USERNAME
andWST_ADMIN_PWD
are 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!