nuricms is a api first content management system written in go.
To install and use NuriCMS as a dependency in your Go project, follow the steps below.
You can add NuriCMS to your Go project using go get
. In your Go project directory, run the following command:
go get github.com/janmarkuslanger/nuricms
After adding NuriCMS as a dependency, you need to create a main.go
file in your project to start the server.
package main
import (
"github.com/janmarkuslanger/nuricms"
"github.com/janmarkuslanger/nuricms/pkg/plugin"
"github.com/janmarkuslanger/nuricms/pkg/config"
)
func main() {
config := config.Config{
Port: "8080",
HookPlugins: []plugin.HookPlugin{},
}
nuricms.Run(config)
}
# Set a basic JWT secret (for development purposes)
export JWT_SECRET=anything
# Generate a secure JWT secret (recommended for production)
export JWT_SECRET=$(openssl rand -base64 32)
go run main.go
If the server gets started and there is no user in the system there will be an admin account added:
- E-Mail: admin@admin.com
- Password: mysecret
The server will now run at http://localhost:8080
. You can change the port by modifying the configuration.
At the core of nuricms are three key concepts:
A collection defines the structure of a content type β such as blog
, product
, or page
. Each collection is made up of multiple fields.
A field describes a single property of a collection, such as title
, price
, or image
. Fields have:
- A name and alias
- A type (e.g.
text
,number
,boolean
,date
,richtext
,asset
,collection
) - Optional settings like default values or whether they are required
Once a collection is created, you can add content entries for it. Each entry stores values for every field defined in the collection.
Values are grouped by their field alias and contain both the field value and its type.
Example: A blog
collection with fields title
and body
might return the following content entry via the API:
{
"id": 29,
"created_at": "2025-07-26T12:23:28.705057+02:00",
"updated_at": "2025-07-26T12:23:28.705057+02:00",
"collection": {
"id": 12
},
"values": {
"title": {
"id": 215,
"value": "A wonderful blog",
"field_type": "Text"
},
"body": {
"id": 216,
"value": "<p>This is my blog body</p>",
"field_type": "RichText"
}
}
}
nuricms
provides a modular plugin system. Plugins can be passed to the CMS via the ServerConfig
at startup and allow extending various parts of the system β such as hooks, routes, or UI components.
Plugins are passed in during server initialization:
config := config.Server{
Port: "8080",
HookPlugins: []nuricms.HookPlugin{
&MyCustomPlugin{},
},
}
nuricms.Run(config)
A HookPlugin
allows you to register functions for specific system events (hooks), such as "content:beforeSave". A hook plugin implements the following interface:
type HookPlugin interface {
Name() string
Register(h *HookRegistry)
}
package plugins
import (
"strings"
"github.com/janmarkuslanger/nuricms/internal/model"
"github.com/janmarkuslanger/nuricms/pkg/plugin"
)
type SlugPlugin struct{}
func (p *SlugPlugin) Name() string {
return "AutoSlug"
}
func (p *SlugPlugin) Register(h *plugin.HookRegistry) {
h.Register("contentValue:beforeSave", func(p any) error {
content := p.(*model.ContentValue)
if content.Field.Alias == "slug" {
content.Value = strings.ToLower(content.Value)
}
return nil
})
}
Then register your plugin in your main.go
:
cfg := config.Server{
Port: "8080",
HookPlugins: []nuricms.HookPlugin{
&myplugin.SlugPlugin{},
},
}
nuricms.Run(cfg)
contentValue:beforeSave
NuriCMS is built on a layered architecture:
βββ cmd/ β main entry point (nuricms server)
βββ internal/
β βββ controller/ β HTTP controllers
β βββ service/ β Business logic
β βββ repository/ β Database access (via GORM)
β βββ model/ β Core entities and types
β βββ handler/ β Generic HTTP handler logic (reused)
βββ pkg/
β βββ config/ β App configuration
β βββ plugin/ β Hook/plugin interface support
βββ templates/ β HTML templates for admin UI
- Controllers handle HTTP and delegate to services (mostly via handler funcs).
- Services coordinate business logic and validate input.
- Repositories access the database.
- Plugin system allows you to register custom logic at runtime.
- All new code must be tested.
- Keep logic modular and covered with unit tests.
- Use dependency injection where applicable to make components testable.
- Follow standard Go formatting (
gofmt
is CI-enforced).
To run tests and generate coverage reports:
go test ./... -coverprofile=cover.out
go tool cover -html=cover.out
For questions or contributions, feel free to open an issue or pull request.