Seed Data with Custom CLI Script
In this chapter, you'll learn how to create a custom CLI script that seeds data into your database.
How to Seed Data#
To seed dummy data, you create a custom CLI script that uses the application's services and workflows to create the data, and run it with the exec command. Use it to prepare a development database, or to load the data a feature needs.
The script should be safe to run more than once, so check that the data doesn't exist before you create it. Otherwise, running it again may fail, for example because of a unique constraint, or create duplicates.
Example: Seed Blog Data#
The following script seeds the Blog Module of the previous chapters. It:
- Stops if the posts exist already.
- Retrieves the sales channel named
Blogwith Query, or creates it with theCreateSalesChannelsWorkflowworkflow of the Sales Channel Module. - Creates three posts with the module's service.
- Links the posts to the sales channel, with the custom column
position.
// Seed creates a sales channel for the blog, three posts, and links the
// posts to the channel. It is registered with the name "seed".
func Seed(ctx context.Context, args typescommon.ExecArgs) error {
c := args.Container
logger := c.Resolve(common.ContainerRegistrationKeys.LOGGER).(*slog.Logger)
q := c.Resolve(common.ContainerRegistrationKeys.QUERY).(*query.Query)
link := c.Resolve(common.ContainerRegistrationKeys.LINK).(*modulessdk.Link)
blogService := c.Resolve(blog.Name).(*blog.BlogModuleService)
logger.Info("Seeding blog data...")
// Do nothing if a previous run already created the posts.
seeded, err := blogService.Internal("Post").List(ctx, rec("slug", "hello-world"), utilsmodulessdk.FindConfig{})
if err != nil {
return err
}
if len(seeded) > 0 {
logger.Info("The blog data is already seeded")
return nil
}
// Reuse the sales channel if it exists.
existing, err := q.Graph(ctx, query.GraphInput{
Entity: "sales_channel",
Fields: []string{"id"},
Filters: rec("name", "Blog"),
}, joiner.Options{})
if err != nil {
return err
}
var channelID any
if len(existing.Data) > 0 {
channelID, _ = existing.Data[0].(*dal.Record).Get("id")
} else {
channels, err := saleschannelworkflows.CreateSalesChannelsWorkflow.Run(ctx, c, saleschannelworkflows.CreateSalesChannelsInput{
SalesChannelsData: []*dal.Record{rec("name", "Blog")},
})
if err != nil {
return err
}
channelID, _ = channels[0].Get("id")
}
posts, err := blogService.Internal("Post").Create(ctx, []*dal.Record{
rec("title", "Hello, world", "slug", "hello-world", "status", "published"),
rec("title", "Release notes", "slug", "release-notes", "status", "published"),
rec("title", "Work in progress", "slug", "work-in-progress"),
})
if err != nil {
return err
}
links := make([]modulessdk.LinkDefinition, len(posts))
for i, post := range posts {
postID, _ := post.Get("id")
links[i] = modulessdk.NewLinkDefinition(
blog.Name, "post_id", postID,
utilsmodulessdk.SALES_CHANNEL, "sales_channel_id", channelID,
)
links[i].Set("data", rec("position", float64(i)))
}
if _, err := link.Create(ctx, links); err != nil {
return err
}
logger.Info(fmt.Sprintf("Seeded %d posts", len(posts)))
return nil
}
In the script:
blog.Nameis the key of the Blog Module, soc.Resolve(blog.Name)returns its service.CreateSalesChannelsWorkflow.Runruns the workflow that creates sales channels with the application's container, so it emits the events and applies the logic that creating a sales channel through the API does.- The link between the posts and the sales channel uses the link that the project defines, with
positionas the custom column.
The imports of the script are:
import (
"context"
"fmt"
"log/slog"
saleschannelworkflows "github.com/hitkey-io/medusago/packages/core/core-flows/src/sales-channel/workflows"
modulessdk "github.com/hitkey-io/medusago/packages/core/modules-sdk/src"
"github.com/hitkey-io/medusago/packages/core/query/src/joiner"
"github.com/hitkey-io/medusago/packages/core/query/src/query"
typescommon "github.com/hitkey-io/medusago/packages/core/types/src/common"
"github.com/hitkey-io/medusago/packages/core/utils/src/common"
"github.com/hitkey-io/medusago/packages/core/utils/src/dal"
utilsmodulessdk "github.com/hitkey-io/medusago/packages/core/utils/src/modules-sdk"
"github.com/hitkey-io/medusago/www/examples/modules/blog"
)
Register and Run the Script#
Register the script in the Scripts option of cli.Run, under the name seed, as in the main.go of the previous chapter. Then, run it:
The script logs the progress of the seeding. Run it a second time: it logs that the data is already seeded, and does nothing.
Test Script#
To check the result, retrieve the posts with their links using Query, in another script, or in your own code. For example, a query on the entity post with the fields id, title, and sales_channels.name returns the posts and the name of their sales channel.