{"id":40570399,"url":"https://github.com/louisevanderlith/husk","last_synced_at":"2026-01-21T01:34:45.435Z","repository":{"id":57485601,"uuid":"138754360","full_name":"louisevanderlith/husk","owner":"louisevanderlith","description":"Husk: Embedded, Object-Oriented Database","archived":false,"fork":false,"pushed_at":"2020-11-02T06:46:40.000Z","size":4834,"stargazers_count":4,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-06-21T15:38:03.488Z","etag":null,"topics":["database-engine","embedded-database","gob","object-oriented-database"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/louisevanderlith.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":"2018-06-26T15:01:20.000Z","updated_at":"2024-06-21T15:38:03.489Z","dependencies_parsed_at":"2022-09-11T15:03:01.580Z","dependency_job_id":null,"html_url":"https://github.com/louisevanderlith/husk","commit_stats":null,"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"purl":"pkg:github/louisevanderlith/husk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/louisevanderlith%2Fhusk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/louisevanderlith%2Fhusk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/louisevanderlith%2Fhusk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/louisevanderlith%2Fhusk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/louisevanderlith","download_url":"https://codeload.github.com/louisevanderlith/husk/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/louisevanderlith%2Fhusk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28621784,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T23:49:58.628Z","status":"ssl_error","status_checked_at":"2026-01-20T23:47:29.996Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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-engine","embedded-database","gob","object-oriented-database"],"created_at":"2026-01-21T01:34:45.378Z","updated_at":"2026-01-21T01:34:45.427Z","avatar_url":"https://github.com/louisevanderlith.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Husk Database Engine [![CodeFactor](https://www.codefactor.io/repository/github/louisevanderlith/husk/badge)](https://www.codefactor.io/repository/github/louisevanderlith/husk)\nHusk was designed to be used directly by Webservice and API based applications without the need for an external storage provider.\n\n## Table of Contents\n* [What is HuskDB?](#what-is-huskdb)\n* [Setup and Installation](#setup-and-installation)\n* [Creating Table/Structure](#creating-tablestructure)\n* [Preparing Seed Data](#preparing-seed-data)\n* [Operations](#operations)\n    * Create\n    * Read\n    * Update\n    * Delete\n    * Calculate\n* [Working with Collections](#working-with-collections)\n* [Advantages over traditional SQL](#advantages-over-traditional-sql)\n* [hsk Tags](#hsk-tags)\n* [Benchmarks](#benchmarks)\n\n### What is HuskDB\nHusk database is an embedded, object-oriented data store which uses Go to interact with records.\nIt is meant to be used by small webservices which fully control the data it hosts.\n\nThis engine attempts to force users to keep business logic close to their required objects, and minimizes entry points and chances for loop holes. ie. when a developer accesses or modifies data outside of the intended scope. \n\nThis includes many sources; \n* Directly modifying data via an external tool. (SQL Server Management Studio, WorkBench, Toad, etc.)\n* Able to access core \"Database\" API in higher-level code, rather than the Logic Layer as intended.  (Front-end, Public facing API End-points)\n* Editing the data file directly.\n\nCreation timestamps sort records internally, and thereafter their traditional \"ID\".\nThe 'Key' is created from the combination of timestamp and id \"0`0\".\nThe Key allows the index to sort the records by creation date, by default. \nThis creates faster access to the most recent records and removes need for sorting after every query.\n\nThe database engine works similar to ISAM, as it stores data on a sequential \"tape\" which lives on disk and held in memory.\nHusk uses an index with pointers to the actual location on the tape for faster access.\n\n### Setup and Installation\nAs HuskDB is a library, which can simply be imported into any Go project.\n\nTo download the Husk module, run;\n\n```$ go get -u github.com/louisevanderlith/husk```\n\nAfter installing the latest module, all that is required is to create a Table/Structure for your data. The following \nsection will provide information on setting up the Table.\n\nMore usage examples can be found under the /tests folder.\n\n### Creating Table/Structure\n* Define data object\nAny type which has the 'Valid()' function, qualifies as a data object, and can be used as a Tabler.\n\n```go\npackage sample\n\nimport \"github.com/louisevanderlith/husk\"\n\n//Person Data Record\ntype Person struct {\n\tName     string `hsk:\"size(50)\"`\n\tAge      int\n\tAccounts []Account\n}\n\n//Valid - To qualify as a Data Record, \n//a struct MUST have a Valid function\nfunc (o Person) Valid() (bool, error) {\n\treturn husk.ValidateStruct(\u0026o)\n}\n```\nIn this sample, we create a \"Person\" object which holds Name, Age and Account information.\n\nThe Name property can only have a value which has a maximum length of 50characters. More information in `hsk` tags can be found later in this document.\n\n* Create a context for the structure\n```go\n    package sample\n    \n    import (\n        \"github.com/louisevanderlith/husk\"\n        \"github.com/louisevanderlith/husk/serials\"\n    )\n    \n    //Context holds the Tables we want to access\n    type Context struct {\n        //People table \n        People husk.Tabler\n    }\n    \n    // NewContext returns the context object with Table values initialized\n    func NewContext() Context {\n        result := Context{}\n    \n        //Creats a new \"Person\" Table, with GobSerializer\n        result.People = husk.NewTable(Person{}, serials.GobSerial{})\n    \n        return result\n    }\n```\n\nWe now have a context which can be utilised by our application\n\n### Preparing Seed Data\nHusk is able to import seed data via a JSON file.\n* Create seed file (people.seed.json) in a folder named 'db'\n* Populate seed data.\n    Seed data should be provided as a JSON array. \n    Example;\n    ```json\n    [\n        {\n            \"Name\": \"Charlie\",\n            \"Age\": 23,\n            \"Accounts\": [{\"Number\": 1234500,\"Balance\": 0.10, \"Transactions\": []}]\n        },\n        {\n            \"Name\": \"Mike\",\n            \"Age\": 48,\n            \"Accounts\": [{\"Number\": 1234501,\"Balance\": 99.10, \"Transactions\": []}]\n        }\n    ]\n  ```\n* Call the Seed function on structures to populate the table\n\n```go \n    func (ctx Context) Seed() {\n        //Seed files can be specified, so that we have data to boot.\n        err := ctx.People.Seed(\"people.seed.json\")\n    \n        if err != nil {\n            panic(err)\n        }\n    \n        ctx.People.Save()\n    }\n```\n\n### Operations\n* Create\nRecords can be created by providing an object to the Create function.\nHusk also supports .CreateMulti which can be used to create many records at once.\n```go\n    p := sample.Person{Name: \"Jimmy\", Age: 25}\n    \n    //Send the object to the context for creation\n    set := ctx.People.Create(p)\n    \n    if set.Error != nil {\n        t.Error(set.Error)\n    }\n    \n    //Persist the changes\n    ctx.People.Save()\n```\n\n* Read\n\nRecords can be located using various methods:\n    \n1. By Key\n    \nThis is the fastest way to locate a record\n    \n```go\n    //parse the string representation of the key to an actual husk.Key\n    k, err := husk.ParseKey(\"0`0\")\n    \n    //find the record with the key\n    rec, err := ctx.People.FindByKey(key)\n```\n    \n2. By Filter\n    \nFilters can be defined as functions which return true/false depending on the conditions.\nHusk also provides a default filter \"husk.Everything\" which can be used to return all records.\n    \n```go\n    type personFilter func(obj Person) bool\n    \n    func (f personFilter) Filter(obj husk.Dataer) bool {\n        return f(obj.(Person))\n    }\n    \n    func ByName(name string) personFilter {\n        return func(obj Person) bool {\n            return obj.Name == name\n        }\n    }\n```\n\nOperations that find records always have to supply page size and index.\nFindFirst is a shortcut for Find(1,1, filter)\n```go\n    //Find People 'ByName', but I only want the first 3 matches\n    result := ctx.People.Find(1, 3, ByName(\"Jimmy\"))\n    \n    //Find 'All' People, but I only want the first 3 matches\n    result = ctx.People.Find(1, 3, husk.Everything())\n```\n\n* Update\n\n```go\n    //First find the desired record, in this case \"ByKey\"\n    p, _ := ctx.People.FindByKey(key)\n    p.Age = 87\n    \n    ctx.People.Update(p)\n    \n    //Persist the changes\n    ctx.People.Save()\n```\n* Delete\n\nRecords can be deleted directly from the database by providing the key of the record to be removed.\n```go\n    //Provide the key to the delete function\n    err := ctx.People.Delete(key)\n    \n    //Persist the changes\n    ctx.People.Save()\n```\n\n* Calculate\n\nThis function can be used in multiple ways to generate datasets.\nThere is no concept of \"SELECT\" in Husk, but it still provides a way of creating custom datasets. \nWhen thinking in terms of traditional SQL, this would be a Stored Procedure.\n\nCalculators can be defined in the same way as Filters, as they function in exactly the same manner. \nFor this reason, we can chain Filters and Calculators to narrow result sets.\n\n```go\n    type personCalc func(result interface{}, obj Person) error\n    \n    // Calc can take a pointer to a result, and update it with values found in the data obj\n    func (f personCalc) Calc(result interface{}, obj husk.Dataer) error {\n        return f(result, obj.(Person))\n    }\n``` \n\nIn the following example we want to get the Total value of a Person's Accounts.\n\n```go\n// SumBalance will return the SUM Balance of a Person's Accounts\nfunc SumBalance() personCalc {\n\treturn func(result interface{}, obj Person) error {\n\t\tansw := float32(0)\n\t\tfor _, acc := range obj.Accounts {\n\t\t\tansw += acc.Balance\n\t\t}\n\n\t\ttotl := result.(*float32)\n\t\t*totl += answ\n\n\t\treturn nil\n\t}\n}\n```\n\nThis example shows how we can get the Lowest Valued Account\n```go\nfunc LowestBalance() personCalc {\n\tmin := float32(9999999)\n\treturn func(result interface{}, obj Person) error {\n        // We can utilise previously defined functions\n        balnce := float32(0)\n\t\terr := SumBalance(\u0026balnce)\n        \n\t\tif balnce \u003c min {\n\t\t\tmin = balnce\n\t\t\tn := result.(*string)\n\t\t\t*n = obj.Name\n\t\t}\n\n\t\treturn nil\n\t}\n}\n```\n\n### Working with Collections\nAfter finding records, you may want to iterate over them to perform other operations.\nHusk provides a way of enumerating these collections.\n```go\n//Find 'Everything', but I only want the first 3\nresult := ctx.People.Find(1, 3, husk.Everything())\n\n//Gets the iterable collection\nrator := result.GetEnumerator()\n\n//Moves to the next item in the collection, until there isn't anything else\nfor rator.MoveNext() {\n\tcurr := rator.Current()\n\tsomeone := curr.Data().(sample.Person)\n\n\tlog.Printf(\"$v\\n\", someone)\n}\n``` \n\n### Advantages over traditional SQL\nHusk doesn't use any Query language, and all data manipulation happens via the code. \nThe immediately avoids SQL injection attacks, and narrows the points of entry.\n\n* No SELECT, Datasets are 'Calculated' and relates more to Stored Procedures, but written in Go.\n* No WHERE Filter using Go functions. Functions can be easily tested and compile with the application.\n* Index Keys are separate from Data, and should only be used to Identify records and create relationships across micro-services.   \n* The database stores records in descending order of creation.  \n* The Primary Key consists of a Timestamp and ID. Quite husk.Key\n* Querying collections always require the 'pagesize' (current page and results per page) to be specified.\n    This forces the developer to always limit the amount of data returned, thus reducing query times.\n* Database embedded into application, no externally hosted services. Greatly reduces the attack surface of the data. \n* Doesn't require a \"ConnectionString\"\n* TDD and Unit Tests can use Husk to create tables that work with the logic. Object-Oriented approach to working with data.\n* Tables can be mapped directly to JSON structures. Big JSON files can be easily analysed.\n* Seed files are JSON documents, so other systems can easily be migrated.  \n* Objects are \"Serialization\" aware, and columns like 'Password' can easily be hidden using `json:\"-\"`\n* Everything related to an object will always remain nested within that object.\n* Database will only consist of one or two tables, micro-services should only control one part of a system.\n\n### hsk Tags\nHusk supports a limited set of tags which can be added to properties.\n* `hsk:\"null\"` - This property may be empty, and is not required.\n* `hsk:\"size(50)\"` - Limits the length of the value to 50 characters.\n\n### Benchmarks\nHistory:\nPlease note these numbers come from our Sample_ETL test, which inserts the same record(16kb) for 20seconds.  \nThis function has since been deprecated, and a better benchmark is yet to be written.\n* v1.0.1 Write: 138rec/s\n    Every record saved creates a new file. Very slow read, and write.\n\n* v1.0.2 Write: 509rec/s (x3.6 improvement)\n    One file stores all records. Greatly improves reads. \n\n* v1.0.3 Write: 1463rec/s (x3 improvement)\n    Index file will only be updated on disk when the context gets saved.\n\n* v1.0.4 Write: 1221rec/s (x1 improvement)\n\n    File operations improved. \n\n* v1.0.5 Write: 2315rec/s (x2 improvement)\n\n    Indexing logic refactored and Keys changed to Pointers. Improved reading.\n\n* v1.0.6 Write: 4314rec/s (x2 improvement)\n\n    Keys are no longer Pointers.\n\n* v1.0.7 Write: Unknown\n\n    General improvements\n\nAverage Write Performance:\n\n* MAC 3167rec/s (Unicorn Power)\n* WINDOWS 2315/rec/s (Spinning Disk, AMD)\n* LINUX 2289rec/s (SSD, Intel i5(2nd gen))\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flouisevanderlith%2Fhusk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flouisevanderlith%2Fhusk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flouisevanderlith%2Fhusk/lists"}