{"id":19652100,"url":"https://github.com/danvergara/seeder","last_synced_at":"2025-04-28T17:30:29.234Z","repository":{"id":37756079,"uuid":"377924272","full_name":"danvergara/seeder","owner":"danvergara","description":"Insert records into a database programatically","archived":false,"fork":false,"pushed_at":"2022-09-22T16:15:58.000Z","size":184,"stargazers_count":42,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-06-19T05:39:14.563Z","etag":null,"topics":["database","database-management","go","golang","mysql","postgresql","seeds","sqlx"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danvergara.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-06-17T18:20:18.000Z","updated_at":"2024-04-28T03:20:18.000Z","dependencies_parsed_at":"2023-01-17T17:00:56.018Z","dependency_job_id":null,"html_url":"https://github.com/danvergara/seeder","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvergara%2Fseeder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvergara%2Fseeder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvergara%2Fseeder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvergara%2Fseeder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danvergara","download_url":"https://codeload.github.com/danvergara/seeder/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224124878,"owners_count":17259746,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","database-management","go","golang","mysql","postgresql","seeds","sqlx"],"created_at":"2024-11-11T15:09:18.207Z","updated_at":"2024-11-11T15:09:18.906Z","avatar_url":"https://github.com/danvergara.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"seeder ![unit tests](https://github.com/danvergara/seeder/actions/workflows/test.yaml/badge.svg) ![linters](https://github.com/danvergara/seeder/actions/workflows/lint.yaml/badge.svg) [![GitHub Release](https://img.shields.io/github/release/danvergara/seeder.svg)](https://github.com/danvergara/seeder/releases)\n=================\n\u003cp align=\"center\"\u003e\n  \u003cimg style=\"float: right;\" src=\"assets/gopher-seeder.png\" alt=\"Seeder logo\"/  width=200\u003e\n\u003c/p\u003e\n\n__Insert records into a database programmatically.__\n\n## Overview\n\nSeeder is a tool used to insert records into your relational database programmatically.\n\n## Features\n\n* Driver agnostic (you can choose whatever database driver you want)\n* sql builder or ORM agnostic (you can run your seeds no matter what library you choose)\n\n## Installation\n\n### CLI:\n\n\u003e **_NOTE:_** The support for the CLI has been deprecated. After a year of actually using the tool, I've realized that this feature is pointless. The user might be better off running their main files by themselves or compiling custom binaries for specific use cases.\n\n### Library:\n\n```sh\n$ go get github.com/danvergara/seeder\n```\n\n## Usage\n\nThe library provides a set of functions as the API:\n\n* Excute\n* ExecuteFunc\n* ExecuteTxFunc\n\n## Execute\n\nCreate an struct and define methods used to insert records into the database on that object.\n\n```go\n// db/seeds/seeds.go\npackage seeds\n\nimport \"github.com/jmoiron/sqlx\"\n\n// Seed struct.\ntype Seed struct {\n\tdb *sqlx.DB\n}\n\n// NewSeed return a Seed with a pool of connection to a dabase.\nfunc NewSeed(db *sqlx.DB) Seed {\n\treturn Seed{\n\t\tdb: db,\n\t}\n}\n```\n\nThis example uses [faker](https://github.com/bxcodec/faker) to generate random data.\n\n```go\n// db/seeds/roles.go\n// db/seeds/users.go\n// db/seeds/products.go\nimport (\n\t\"log\"\n\t\"math/rand\"\n\n\t\"github.com/bxcodec/faker/v3\"\n)\n\n// RolesSeed seeds roles data.\nfunc (s Seed) RolesSeed() {\n\tvar err error\n\n\t_, err = s.db.Exec(`INSERT INTO roles(name) VALUES ($1)`, \"admin\")\n\tif err != nil {\n\t\tlog.Fatalf(\"error seeding roles: %v\", err)\n\t}\n\n\t_, err = s.db.Exec(`INSERT INTO roles(name) VALUES ($1)`, \"user\")\n\tif err != nil {\n\t\tlog.Fatalf(\"error seeding roles: %v\", err)\n\t}\n}\n\n// UsersSeed seeds roles data.\nfunc (s Seed) UsersSeed() {\n\tvar id int\n\tvar err error\n\n\terr = s.db.Get(\u0026id, `SELECT id FROM roles WHERE name = 'admin'`)\n\tif err != nil {\n\t\tlog.Fatalf(\"error querying the roles table: %v\", err)\n\t}\n\n\tfor i := 0; i \u003c 50; i++ {\n\t\t_, err = s.db.Exec(`INSERT INTO users(username, first_name, last_name, role_id) VALUES ($1, $2, $3, $4)`, faker.Username(), faker.FirstName(), faker.LastName(), id)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"error seeding roles: %v\", err)\n\t\t}\n\t}\n\n\terr = s.db.Get(\u0026id, `SELECT id FROM roles WHERE name = 'user'`)\n\tif err != nil {\n\t\tlog.Fatalf(\"error querying the roles table: %v\", err)\n\t}\n\n\tfor i := 0; i \u003c 50; i++ {\n\t\t_, err = s.db.Exec(`INSERT INTO users(username, first_name, last_name, role_id) VALUES ($1, $2, $3, $4)`, faker.Username(), faker.FirstName(), faker.LastName(), id)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"error seeding roles: %v\", err)\n\t\t}\n\t}\n}\n\n// ProductsSeed seeds product data.\nfunc (s Seed) ProductsSeed() {\n\tfor i := 0; i \u003c 100; i++ {\n\t\tvar err error\n\n\t\t_, err = s.db.Exec(`INSERT INTO products(name, price) VALUES ($1, $2)`, faker.Word(), rand.Float32())\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"error seeding products: %v\", err)\n\t\t}\n\t}\n}\n```\n\nThen, instantiate the `Seed` struct. The `Execute` function is gonna access to all the methods attached to `Seed`.\n\n```go\n//  db/main.go\nimport (\n\t\"log\"\n\n\t\"github.com/danvergara/seeder/db/seeds\"\n\t\"github.com/danvergara/seeder\"\n\t\"github.com/jmoiron/sqlx\"\n\n\t// postgres driver.\n\t_ \"github.com/lib/pq\"\n)\n\nfunc main() {\n\tdb, err := sqlx.Open(\"postgres\", \"postgres-url\")\n\tif err != nil {\n\t\tlog.Fatalf(\"error opening a connection with the database %s\\n\", err)\n\t}\n\n\ts := seeds.NewSeed(db)\n\n\tif err := seeder.Execute(s); err != nil {\n\t\tlog.Fatalf(\"error seeding the db %s\\n\", err)\n\t}\n}\n\n```\nUnfortunately, due to `Seeder` uses reflection to guess the number and the name of the methods, the execution of methods is sorted in lexicographic order. So, if you chose this approach, make sure the order of the desired execution matches the lexicographic order of the defined methods. There's another way to deal with this limitation:\n\n```go\nimport (\n\t\"log\"\n\t\"math/rand\"\n\n\t\"github.com/bxcodec/faker/v3\"\n)\n\nfunc (s Seed) rolesSeed() {\n\tvar err error\n\n\t_, err = s.db.Exec(`INSERT INTO roles(name) VALUES ($1)`, \"admin\")\n\tif err != nil {\n\t\tlog.Fatalf(\"error seeding roles: %v\", err)\n\t}\n\n\t_, err = s.db.Exec(`INSERT INTO roles(name) VALUES ($1)`, \"user\")\n\tif err != nil {\n\t\tlog.Fatalf(\"error seeding roles: %v\", err)\n\t}\n}\n\nfunc (s Seed) usersSeed() {\n\tvar id int\n\tvar err error\n\n\terr = s.db.Get(\u0026id, `SELECT id FROM roles WHERE name = 'admin'`)\n\tif err != nil {\n\t\tlog.Fatalf(\"error querying the roles table: %v\", err)\n\t}\n\n\tfor i := 0; i \u003c 50; i++ {\n\t\t_, err = s.db.Exec(`INSERT INTO users(username, first_name, last_name, role_id) VALUES ($1, $2, $3, $4)`, faker.Username(), faker.FirstName(), faker.LastName(), id)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"error seeding roles: %v\", err)\n\t\t}\n\t}\n\n\terr = s.db.Get(\u0026id, `SELECT id FROM roles WHERE name = 'user'`)\n\tif err != nil {\n\t\tlog.Fatalf(\"error querying the roles table: %v\", err)\n\t}\n\n\tfor i := 0; i \u003c 50; i++ {\n\t\t_, err = s.db.Exec(`INSERT INTO users(username, first_name, last_name, role_id) VALUES ($1, $2, $3, $4)`, faker.Username(), faker.FirstName(), faker.LastName(), id)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"error seeding roles: %v\", err)\n\t\t}\n\t}\n}\n\nfunc (s Seed) productsSeed() {\n\tfor i := 0; i \u003c 100; i++ {\n\t\tvar err error\n\n\t\t_, err = s.db.Exec(`INSERT INTO products(name, price) VALUES ($1, $2)`, faker.Word(), rand.Float32())\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"error seeding products: %v\", err)\n\t\t}\n\t}\n}\n\nfunc (s Seed) PopulateDB() {\n\ts.rolesSeed()\n\ts.usersSeed()\n\ts.productsSeed()\n}\n```\n\nBy making the methods unexported and defining them in a specific order in another exported method, bypassing the limitation imposed by the `reflect` package.\n\nThis approach has a problem we recently spotted and which is that if an insertion errors out, the previous insertions can't be rollback. To tackle this problem down, we can use TXs.\n\n```go\n// db/seeds/seeds.go\npackage seeds\n\nimport \"github.com/jmoiron/sqlx\"\n\n// Seed struct.\ntype Seed struct {\n\ttx *sqlx.Tx\n}\n\n// NewSeed return a Seed with a pool of connection to a dabase.\nfunc NewSeed(tx *sqlx.Tx) Seed {\n\treturn Seed{\n\t\ttx: tx,\n\t}\n}\n```\nThen, handle the tx based on the error value:\n\n```go\n//  db/main.go\nimport (\n\t\"log\"\n\n\t\"github.com/danvergara/seeder/db/seeds\"\n\t\"github.com/danvergara/seeder\"\n\t\"github.com/jmoiron/sqlx\"\n\n\t// postgres driver.\n\t_ \"github.com/lib/pq\"\n)\n\nfunc main() {\n\tdb, err := sqlx.Open(\"postgres\", \"postgres-url\")\n\tif err != nil {\n\t\tlog.Fatalf(\"error opening a connection with the database %s\\n\", err)\n\t}\n\n\ttx, err := db.Beginx()\n\tif err != nil {\n\t\tlog.Fatalf(\"error creating a tx %s\\n\", err)\n\t}\n\n\ts := seeds.NewSeed(tx)\n\n\tif err := seeder.Execute(s); err != nil {\n        tx.Rollback()\n\t}\n\n    tx.Commit()\n}\n```\n\n\n## ExecuteFunc\n\nIn case you want to direclty work with an instance of `sql.DB` from `database/sql`, you can use `ExecuteFunc` which allows you to pass one or more functions to the `ExecuteFunc` function, along with a pointer to a `sql.DB` instance.\n\nThe functions you use to seed the database need to have the following signature:\n\n```go\nfunc(*sql.DB) error\n```\n\n```go\n// db/seeds/seeds.go \n\nimport (\n\t\"database/sql\"\n\t\"math/rand\"\n\n\t\"github.com/bxcodec/faker/v3\"\n\t\"github.com/jmoiron/sqlx\"\n)\n\nfunc PopulateDB(db *sql.DB) error {\n\tvar id int\n\n\t// inserts roles.\n\tif _, err := db.Exec(`INSERT INTO roles(name) VALUES ($1)`, \"admin\"); err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := db.Exec(`INSERT INTO roles(name) VALUES ($1)`, \"user\"); err != nil {\n\t\treturn err\n\t}\n\n\t// inserts users with admin permissions.\n\tif err := db.QueryRow(`SELECT id FROM roles WHERE name = 'admin'`).Scan(\u0026id); err != nil {\n\t\treturn err\n\t}\n\n\tfor i := 0; i \u003c 50; i++ {\n\t\t_, err := db.Exec(\n\t\t\t`INSERT INTO users(username, first_name, last_name, role_id) VALUES ($1, $2, $3, $4)`,\n\t\t\tfaker.Username(),\n\t\t\tfaker.FirstName(),\n\t\t\tfaker.LastName(),\n\t\t\tid,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// inserts users with regular permissions.\n\tif err := db.QueryRow(`SELECT id FROM roles WHERE name = 'user'`).Scan(\u0026id); err != nil {\n\t\treturn err\n\t}\n\n\tfor i := 0; i \u003c 50; i++ {\n\t\t_, err := db.Exec(\n\t\t\t`INSERT INTO users(username, first_name, last_name, role_id) VALUES ($1, $2, $3, $4)`,\n\t\t\tfaker.Username(),\n\t\t\tfaker.FirstName(),\n\t\t\tfaker.LastName(),\n\t\t\tid,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// inserts products.\n\tfor i := 0; i \u003c 100; i++ {\n\t\tvar err error\n\n\t\tif _, err = db.Exec(\n\t\t\t`INSERT INTO products(name, price) VALUES ($1, $2)`, faker.Word(), rand.Float32(),\n\t\t); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n```\n\nNow, you can pass the function previously define to `ExecuteFunc`, along with your database connection.\n\n```go\n//  db/main.go\nimport (\n\t\"log\"\n\n\t\"github.com/danvergara/seeder/db/seeds\"\n\t\"github.com/danvergara/seeder\"\n\t\"github.com/jmoiron/sqlx\"\n\n\t// postgres driver.\n\t_ \"github.com/lib/pq\"\n)\n\nfunc main() {\n\tdb, err := sqlx.Open(\"postgres\", \"postgres-url\")\n\tif err != nil {\n\t\tlog.Fatalf(\"error opening a connection with the database %s\\n\", err)\n\t}\n\n\t// Here's where you pass the functions you want to use to insert new records into the database.\n\tif err := seeder.ExecuteFunc(db, seeds.PopulateDB); err != nil {\n\t\tlog.Fatalf(\"error seeding the db %s\\n\", err)\n\t}\n}\n```\n\n## EexcuteTxFunc\n\nThe function recieves a `Tx` as a parameter, along with a list of functions which accept TXs, too.\n\nTry this out:\n```go\n//  db/main.go\nimport (\n\t\"log\"\n\n\t\"github.com/danvergara/seeder/db/seeds\"\n\t\"github.com/danvergara/seeder\"\n\t\"github.com/jmoiron/sqlx\"\n\n\t// postgres driver.\n\t_ \"github.com/lib/pq\"\n)\n\nfunc main() {\n\tdb, err := sqlx.Open(\"postgres\", \"postgres-url\")\n\tif err != nil {\n\t\tlog.Fatalf(\"error opening a connection with the database %s\\n\", err)\n\t}\n\n\ttx, err := db.Begin()\n\tif err != nil {\n\t\tlog.Fatalf(\"error creating a tx %s\\n\", err)\n\t}\n\n\t// Here's where you pass the functions you want to use to insert new records into the database.\n\tif err := seeder.ExecuteTxFunc(tx, seeds.PopulateTx); err != nil {\n\t\tlog.Fatalf(\"error seeding the db %s\\n\", err)\n\t}\n}\n```\n\n## Instructions to run the seeds\n\nThere is two options to run the seeds:\n\n1. Run the main file:\n\n```sh\n$ go run ./example/main.go\n```\n\n```\n └── db\n    ├── main.go\n    └── seeds\n        ├── products.go\n        ├── roles.go\n        ├── seeds.go\n        └── users.go\n```\n\n2. Compile the project:\n\n```sh\n$ cd example \u0026\u0026 go build \u0026\u0026 ./example\n```\n\n## Contribute\n\n- Fork this repository\n- Create a new feature branch for a new functionality or bugfix\n- Commit your changes\n- Execute test suite\n- Push your code and open a new pull request\n- Use [issues](https://github.com/danvergara/seeder/issues) for any questions\n\n## License\nApache-2.0 License. See [LICENSE](LICENSE) file for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanvergara%2Fseeder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanvergara%2Fseeder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanvergara%2Fseeder/lists"}