{"id":22980516,"url":"https://github.com/kcartlidge/ng","last_synced_at":"2026-05-14T22:43:39.071Z","repository":{"id":61586503,"uuid":"552792114","full_name":"kcartlidge/ng","owner":"kcartlidge","description":"Generate strongly-typed Go database access code directly from your Postgres database schema.","archived":false,"fork":false,"pushed_at":"2025-01-12T15:53:48.000Z","size":212751,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-02-08T00:41:30.625Z","etag":null,"topics":["database","generator","go","sql"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kcartlidge.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-17T08:31:11.000Z","updated_at":"2025-01-12T15:53:40.000Z","dependencies_parsed_at":"2024-07-06T00:01:41.553Z","dependency_job_id":"a12ea8f6-5080-40be-b7c6-28ad11248ebb","html_url":"https://github.com/kcartlidge/ng","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kcartlidge%2Fng","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kcartlidge%2Fng/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kcartlidge%2Fng/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kcartlidge%2Fng/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kcartlidge","download_url":"https://codeload.github.com/kcartlidge/ng/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246792469,"owners_count":20834920,"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","generator","go","sql"],"created_at":"2024-12-15T01:43:46.815Z","updated_at":"2025-10-29T05:32:37.309Z","avatar_url":"https://github.com/kcartlidge.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Near Gothic v1.2.0\n\nGenerate strongly-typed Go database access code directly from your Postgres database schema.\n\nA simpler version of tools like *SQL Boiler*.\nThe created code supports filtering and sorting, comments, indexes, length restrictions, nullability, and more - with minimal setup needed.\n\n## STATUS\n\n*Stable. Usable. Beta.*\n\n- [AGPL license](./LICENSE)\n- [CHANGELOG](./CHANGELOG.md)\n\n## Performance\n\nOn a 10th Gen Core i5 running Windows 11 Pro, using PostgreSQL 15 with 3 tables, it generates (and formats) a code-base in around **600ms** when averaged over a dozen runs.\n\nGo is fast. On most modern machines even with larger collections of tables it should complete within seconds.\n\n# Contents\n\n- [Prerequisites](#prerequisites)\n  - [Expectations](#expectations)\n- [Running](#running)\n- [How Near Gothic works](#how-near-gothic-works)\n  - [Generated folder structure](#generated-folder-structure)\n  - [Example of using the generated code](#example-of-using-the-generated-code)\n- [Building cross-platform binaries](#building-cross-platform-binaries)\n\n## Prerequisites\n\nYou'll need a PostgreSQL database and valid connection details.\nThe connection string should be an environment value in *pgx* format.\nThe default environment variable name is `DB_CONNSTR`.\n\n``` sh\nexport DB_CONNSTR=\"host=127.0.0.1 port=5432 dbname=example user=example password=example sslmode=disable\"\n```\n\n*(This is an example, not a revealed secret.)*\n\n[Here's a Postgres SQL script for a (small) example database](./postgres.sql).\n\nNote that upon successfully running, the generated Go code will include its own `postgres.sql` file.  That file will contain scripts suitable for recreating the database entities found/used when it ran.\n\n### Expectations\n\nYour database *must* follow certain conventions.\nThis may change in the future, but currently the following applies:\n\n- TABLES\n  - Tables must have a primary key\n    - *Only one column is allowed (no composite keys)*\n      - This is not currently enforced but is expected\n      - Ignoring this risks data corruption\n  - Table names must be snaked-lowercase\n    - For example `another_thing`, not `AnotherThing` or `Another_Thing`\n  - Tables may have comments, which are incorporated into the generated entities\n- COLUMNS\n  - Column names must also be snaked-lowercase\n    - For example `account_id`, not `AccountID` or `accountid`\n  - Most common database column types are supported\n    - A list will be included upon first full release\n  - Columns may have comments, which are incorporated into the generated entities\n\n## Running\n\nThere are pre-built cross-platform binaries in the [`src/builds`](./src/builds) folder.\nWhen run they will display the command requirements with details and examples (as shown below).\n\n```\nUSAGE\n  ng [-w] [-env \u003cvalue\u003e] [-schema \u003cvalue\u003e] -folder \u003cvalue\u003e -module \u003cvalue\u003e -repo \u003cvalue\u003e\n\nARGUMENTS\n  -w                  overwrite any existing destination folder?\n  -env \u003cvalue\u003e        connection string environment variable (default `DB_CONNSTR`)\n  -schema \u003cvalue\u003e     the Postgres database schema to scan (default `public`)\n  -folder \u003cvalue\u003e  *  the *parent* module's folder (eg `~/Source/App`)\n  -module \u003cvalue\u003e  *  the *parent* Go module name (eg `kcartlidge/app`)\n  -repo \u003cvalue\u003e    *  the short package name for generated code (eg `data`)\n\n  * means the argument is required\n\nEXAMPLE\n  ng -w -env DB_CONNSTR -schema example -module kcartlidge/app -folder ~/Source/App -repo Data\n\nThe `env` connection string should be suitable for `jackc/pgx`.\n\nGenerated code is placed in a new (`repo`) folder within the\n`parent` folder. For the example above `~/Source/App` + `Data`\nmeans code goes into `~/Source/App/Data`.\n\nThe new code will assume it is in a package named as `module`\nplus `repo` (in lower case). For the example above, that means\n`kcartlidge/app` + `Data` gives ``kcartlidge/app/data`.\n```\n\nThe created `README.md` file will include the command you used when generating the code.\n\n## How Near Gothic works\n\n- It uses the named environment variable (`-env`) to connect to the database\n- It scans the provided PostgreSQL schema (`-schema`)\n- It generates code *in a subfolder* of your app\n\nYou get a folder structure with the following:\n\n- Commented code\n- A set of entities, one per database table\n  - SQL table comments implemented as Go comments\n  - Generated property comments\n    - Max length, primary key flag, sortable/filterable\n  - Extra constructors\n    - Construct from a *pgx* row\n    - Construct from an HTTP POST\n  - Column attributes for JSON, SQL, display, and slugs\n  - Validation based on SQL column length\n- A connection package\n- A package of strongly-typed repositories\n  - Includes typed querying based on column details\n  - Typed sorting for indexed columns (untyped support for unindexed ones)\n  - Typed null checks for nullable fields\n- A `README.md` detailing what the repo contains\n- A `USING.md` detailing how to use the repo\n- An emergency SQL script to recreate the entities\n  - Comments, constraints, keys, defaults, and more\n- JSON dump file detailing what was scanned from the database\n  - Useful for your own further automated processing\n\n### Generated folder structure\n\nHere's a high-level breakdown of what the files/folders contain, based on the example values used above for the folder, module, and repo package name.\n\n```\n~/Source/App                   // target `folder` (parent module)\n  go.mod                       // example parent module file\n  go.sum                       // example parent module sum file\n  main.go                      // example parent code file\n\n  /data                        // root (`repo`) of the generated content\n    /connection\n      connection.go            // class for db connection\n    /entities\n      account-setting.go       // the 'account_setting' db table\n      account.go               // the 'account' db table\n      setting.go               // the 'setting' db table\n    /repos\n      account-repo.go          // the 'account' repository\n      account-setting-repo.go  // the 'account-setting' repository\n      repo-base.go             // shared repository functionality\n      setting-repo.go          // the 'setting' repository\n    /support\n      support.go               // support functions\n    dump.json                  // JSON dump of the schema\n    postgres.sql               // SQL to recreate the entities\n    README.md                  // overview of the generated code\n    USING.md                   // details of how to use the code\n```\n\n### Example of using the generated code\n\nNearGothic does not create modules.\nIt generates a stand-alone package written into a subfolder of your app's module.\nThat package, when imported, provides strongly typed access to the database.\n\nThe example below is using a generated repo (`kcartlidge/app/data`) derived from a database containing an `account` table (amongst others).\n\nIt shows the `connection` being obtained by a connection string stored in an environment variable. It then uses that to create an instance of an `AccountRepo` via which it fetches the first 3 accounts sorted by the email address in reverse alphabetical order (for no obvious reason other than to show the somewhat-fluent query capability).\n\nRepos are automatically created for each table found in your Postgres schema. Column types are mapped to Go types. SQL comments show as Go comments. Basic validation based on nullability and length is included, and utility methods for both filtering and sorting are added for each column that has an index (non-column-specific alternatives are also provided).\n\n**Important note:** repo instances retain any filters/sorts between calls, allowing you to (for example) add a `UserId` restriction at the start of using a repo and be confident that restriction will apply to further operations.\nFor this same reason it is imperative that each scope creates it's own instance of any repos for use, as sharing instances can cause 'bleeding' of sorts/filters across operations leading to unexpected results.\n(Repos are lightweight; the overhead is minimal and instances can share a connection.)\n\n``` go\npackage main\n\nimport (\n    \"fmt\"\n    \"kcartlidge/app/data/connection\"\n    \"kcartlidge/app/data/entities\"\n    \"kcartlidge/app/data/repos\"\n    \"log\"\n    \"os\"\n    \"time\"\n)\n\nfunc main() {\n    // Limit queries to 100 rows for performance/scalability.\n    // This can be overridden on individual calls.\n    connection.MaxRows = 100\n\n    // This shows SQL statements on the console.\n    connection.DebugMode = true\n\n    // Connect using the connection string in the \"DB_CONNSTR\" environment variable.\n    // This will exit if no connection is possible.\n    envName := \"DB_CONNSTR\"\n    connectionString, hasEnv := os.LookupEnv(envName)\n    if !hasEnv {\n        log.Fatalf(\"Environment variable `%s` not found\", envName)\n    }\n    conn := connection.NewConnection(connectionString)\n    accounts := repos.NewAccountRepo(conn)\n\n    // Check if we've already created the test account. If not, add it.\n    exists, err := accounts.WhereEmailAddress(\"=\", \"email@example.com\").List()\n    if err == nil \u0026\u0026 len(exists) == 0 {\n        now := time.Now().UTC()\n        _, err = accounts.Insert(entities.Account{\n            EmailAddress: \"email@example.com\",\n            DisplayName:  \"Example\",\n            CreatedAt:    \u0026now,\n            UpdatedAt:    \u0026now,\n            DeletedAt:    nil,\n        })\n    }\n    if err != nil {\n        log.Fatalln(err.Error())\n    }\n\n    // Start a new repo and fetch the first 3 accounts in reverse email address order.\n    // Obviously we've only created one, but remember that this is example code.\n    // These lines will not build if your database tables differ (they probably do).\n    fmt.Println(\"FIRST FEW ACCOUNTS\")\n    show(accounts.\n        WhereId(\"\u003c\", 4).\n        WhereEmailAddressIsNull(false).\n        ReverseByEmailAddress().\n        List())\n\n    // Deal with a specific account, using a new repo to reset the filter/sort.\n    // Could also call accounts.ResetConditions() and/or accounts.ResetSorting() instead.\n    fmt.Println(\"FIND ACCOUNT\")\n    accounts = repos.NewAccountRepo(conn)\n    show(accounts.\n        WhereEmailAddress(\"=\", \"email@example.com\").\n        List())\n\n    // Query a view. The repo will not contain any insert/update/delete code.\n    // Important: columns in views will always be nullable according to Postgres.\n    // This applies both for querying and for the results.\n    fmt.Println(\"QUERY A VIEW\")\n    activeAccounts := repos.NewActiveAccountRepo(conn)\n    notEmail := \"email@example.com\"\n    show(activeAccounts.\n        WhereEmailAddress(\"\u003c\u003e\", \u0026notEmail).\n        SortByDisplayName().\n        List())\n}\n\n// show displays the data or exists if there's an error.\nfunc show(data interface{}, err error) {\n    if err != nil {\n        log.Fatal(err.Error())\n    }\n    fmt.Println(data)\n    fmt.Println()\n}\n```\n\nIf you want to see the database operations, including the SQL that was generated, you can switch on debugging output:\n\n``` go\nconnection.DebugMode = true\n```\n\n---\n\n## Building cross-platform binaries\n\n*Skip this if you are just intending to use Near Gothic rather than contribute to it.*\n\nThe [`src/scripts`](./src/scripts) folder has scripts to be run *on* Linux, Mac, and Windows. Use the one for the system *you* are currently running.\nEach of those scripts will produce builds for the three supported platforms at once, and automatically place them in the expected [`src/builds`](./src/builds) folder.\n\nLinux/Mac:\n\n``` shell\ncd src\n./scripts/macos.sh\n./scripts/linux.sh\n```\n\nWindows:\n\n``` shell\ncd src\nscripts\\windows.bat\n```\n\nWhen you do new builds update the version number in this file's title and in `main.go`.\n\n## Generating local builds during development\n\nYou can also do one-off local builds and run them.\nChange the paths and output executables as necessary in the following in order to reflect *your current* platform.\n\nLinux/Mac:\n\n``` shell\ncd src\ngo build -o builds/macos-arm/ng \u0026\u0026 ./builds/macos-arm/ng -env DB_CONNSTR -schema example -module kcartlidge/ng-test -folder ../../ng-test -repo Data -w\n```\n\nWindows:\n\n``` shell\ncd src\nscripts\\windows.bat\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkcartlidge%2Fng","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkcartlidge%2Fng","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkcartlidge%2Fng/lists"}