# Design Source: https://docs.hypermode.com/badger/design Architected for fast key-value storage in Go We wrote Badger with these design goals in mind: * Write a key-value database in pure Go * Use latest research to build the fastest KV database for data sets spanning terabytes * Optimize for modern storage devices Badger’s design is based on a paper titled [WiscKey: Separating Keys from Values in SSD-conscious Storage](https://www.usenix.org/system/files/conference/fast16/fast16-papers-lu.pdf). ## References The following blog posts are a great starting point for learning more about Badger and the underlying design principles: * [Introducing Badger: A fast key-value store written natively in Go](https://dgraph.io/blog/post/badger/) * [Make Badger crash resilient with ALICE](https://dgraph.io/blog/post/alice/) * [Badger vs LMDB vs BoltDB: Benchmarking key-value databases in Go](https://dgraph.io/blog/post/badger-lmdb-boltdb/) * [Concurrent ACID Transactions in Badger](https://dgraph.io/blog/post/badger-txn/) ## Comparisons | Feature | Badger | RocksDB | BoltDB | | ----------------------------- | -------------------------------------- | ---------------------------- | ------- | | Design | LSM tree with value log | LSM tree only | B+ tree | | High Read throughput | Yes | No | Yes | | High Write throughput | Yes | Yes | No | | Designed for SSDs | Yes (with latest research1) | Not specifically2 | No | | Embeddable | Yes | Yes | Yes | | Sorted KV access | Yes | Yes | Yes | | Pure Go (no Cgo) | Yes | No | Yes | | Transactions | Yes | Yes | Yes | | ACID-compliant | Yes, concurrent with SSI3 | No | Yes | | Snapshots | Yes | Yes | Yes | | TTL support | Yes | Yes | No | | 3D access (key-value-version) | Yes4 | No | No | 1 The WiscKey paper (on which Badger is based) saw big wins with separating values from keys, significantly reducing the write amplification compared to a typical LSM tree. 2 RocksDB is an SSD-optimized version of LevelDB, which was designed specifically for rotating disks. As such RocksDB's design isn't aimed at SSDs. 3 SSI: Serializable Snapshot Isolation. For more details, see the blog post [Concurrent ACID Transactions in Badger](https://dgraph.io/blog/post/badger-txn/) 4 Badger provides direct access to value versions via its Iterator API. Users can also specify how many versions to keep per key via Options. ## Benchmarks We've run comprehensive benchmarks against RocksDB, BoltDB, and LMDB. The benchmarking code with detailed logs are in the [badger-bench](https://github.com/dgraph-io/badger-bench) repo. # Overview Source: https://docs.hypermode.com/badger/overview Welcome to the Badger docs! ## What is Badger? {/* */} BadgerDB is an embeddable, persistent, and fast key-value (KV) database written in pure Go. It's the underlying database for [Dgraph](https://dgraph.io), a fast, distributed graph database. It's meant to be an efficient alternative to non-Go-based key-value stores like RocksDB. ## Changelog We keep the [repo Changelog](https://github.com/hypermodeinc/badger/blob/main/CHANGELOG.md) up to date with each release. # Quickstart Source: https://docs.hypermode.com/badger/quickstart Everything you need to get started with Badger ## Prerequisites * [Go](https://go.dev/doc/install) - v1.23 or higher * Text editor - we recommend [VS Code](https://code.visualstudio.com/) * Terminal - access Badger through a command-line interface (CLI) ## Installing To start using Badger, run the following command to retrieve the library. ```sh go get github.com/dgraph-io/badger/v4 ``` Then, install the Badger command line utility into your `$GOBIN` path. ```sh go install github.com/dgraph-io/badger/v4/badger@latest ``` ## Opening a database The top-level object in Badger is a `DB`. It represents multiple files on disk in specific directories, which contain the data for a single database. To open your database, use the `badger.Open()` function, with the appropriate options. The `Dir` and `ValueDir` options are mandatory and you must specify them in your client. To simplify, you can set both options to the same value. Badger obtains a lock on the directories. Multiple processes can't open the same database at the same time. ```go package main import ( "log" badger "github.com/dgraph-io/badger/v4" ) func main() { // Open the Badger database located in the /tmp/badger directory. // It will be created if it doesn't exist. db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) if err != nil { log.Fatal(err) } defer db.Close() // your code here } ``` ### In-memory/diskless mode By default, Badger ensures all data persists to disk. It also supports a pure in-memory mode. When Badger is running in this mode, all data remains in memory only. Reads and writes are much faster, but Badger loses all stored data in the case of a crash or close. To open badger in in-memory mode, set the `InMemory` option. ```go opt := badger.DefaultOptions("").WithInMemory(true) ``` ### Encryption mode If you enable encryption in Badger, you also need to set the index cache size. The cache improves the performance. Otherwise, reads can be very slow with encryption enabled. For example, to set a `100 Mb` cache: ```go opts.IndexCache = 100 << 20 // 100 mb or some other size based on the amount of data ``` ## Transactions ### Read-only transactions To start a read-only transaction, you can use the `DB.View()` method: ```go err := db.View(func(txn *badger.Txn) error { // your code here return nil }) ``` You can't perform any writes or deletes within this transaction. Badger ensures that you get a consistent view of the database within this closure. Any writes that happen elsewhere after the transaction has started aren't seen by calls made within the closure. ### Read-write transactions To start a read-write transaction, you can use the `DB.Update()` method: ```go err := db.Update(func(txn *badger.Txn) error { // Your code here… return nil }) ``` Badger allows all database operations inside a read-write transaction. Always check the returned error value. If you return an error within your closure it's passed through. An `ErrConflict` error is reported in case of a conflict. Depending on the state of your app, you have the option to retry the operation if you receive this error. An `ErrTxnTooBig` is reported in case the number of pending writes/deletes in the transaction exceeds a certain limit. In that case, it's best to commit the transaction and start a new transaction immediately. Here is an example (we aren't checking for errors in some places for simplicity): ```go updates := make(map[string]string) txn := db.NewTransaction(true) for k,v := range updates { if err := txn.Set([]byte(k),[]byte(v)); err == badger.ErrTxnTooBig { _ = txn.Commit() txn = db.NewTransaction(true) _ = txn.Set([]byte(k),[]byte(v)) } } _ = txn.Commit() ``` ### Managing transactions manually The `DB.View()` and `DB.Update()` methods are wrappers around the `DB.NewTransaction()` and `Txn.Commit()` methods (or `Txn.Discard()` in case of read-only transactions). These helper methods start the transaction, execute a function, and then safely discard your transaction if an error is returned. This is the recommended way to use Badger transactions. However, sometimes you may want to manually create and commit your transactions. You can use the `DB.NewTransaction()` function directly, which takes in a boolean argument to specify whether a read-write transaction is required. For read-write transactions, it's necessary to call `Txn.Commit()` to ensure the transaction is committed. For read-only transactions, calling `Txn.Discard()` is sufficient. `Txn.Commit()` also calls `Txn.Discard()` internally to cleanup the transaction, so just calling `Txn.Commit()` is sufficient for read-write transaction. However, if your code doesn’t call `Txn.Commit()` for some reason (for e.g it returns prematurely with an error), then please make sure you call `Txn.Discard()` in a `defer` block. Refer to the code below. ```go // Start a writable transaction. txn := db.NewTransaction(true) defer txn.Discard() // Use the transaction... err := txn.Set([]byte("answer"), []byte("42")) if err != nil { return err } // Commit the transaction and check for error. if err := txn.Commit(); err != nil { return err } ``` The first argument to `DB.NewTransaction()` is a boolean stating if the transaction should be writable. Badger allows an optional callback to the `Txn.Commit()` method. Normally, the callback can be set to `nil`, and the method will return after all the writes have succeeded. However, if this callback is provided, the `Txn.Commit()` method returns as soon as it has checked for any conflicts. The actual writing to the disk happens asynchronously, and the callback is invoked once the writing has finished, or an error has occurred. This can improve the throughput of the app in some cases. But it also means that a transaction isn't durable until the callback has been invoked with a `nil` error value. ## Using key/value pairs To save a key/value pair, use the `Txn.Set()` method: ```go err := db.Update(func(txn *badger.Txn) error { err := txn.Set([]byte("answer"), []byte("42")) return err }) ``` Key/Value pair can also be saved by first creating `Entry`, then setting this `Entry` using `Txn.SetEntry()`. `Entry` also exposes methods to set properties on it. ```go err := db.Update(func(txn *badger.Txn) error { e := badger.NewEntry([]byte("answer"), []byte("42")) err := txn.SetEntry(e) return err }) ``` This sets the value of the `"answer"` key to `"42"`. To retrieve this value, we can use the `Txn.Get()` method: ```go err := db.View(func(txn *badger.Txn) error { item, err := txn.Get([]byte("answer")) handle(err) var valNot, valCopy []byte err := item.Value(func(val []byte) error { // This func with val would only be called if item.Value encounters no error. // Accessing val here is valid. fmt.Printf("The answer is: %s\n", val) // Copying or parsing val is valid. valCopy = append([]byte{}, val...) // Assigning val slice to another variable is NOT OK. valNot = val // Do not do this. return nil }) handle(err) // DO NOT access val here. It is the most common cause of bugs. fmt.Printf("NEVER do this. %s\n", valNot) // You must copy it to use it outside item.Value(...). fmt.Printf("The answer is: %s\n", valCopy) // Alternatively, you could also use item.ValueCopy(). valCopy, err = item.ValueCopy(nil) handle(err) fmt.Printf("The answer is: %s\n", valCopy) return nil }) ``` `Txn.Get()` returns `ErrKeyNotFound` if the value isn't found. Please note that values returned from `Get()` are only valid while the transaction is open. If you need to use a value outside of the transaction then you must use `copy()` to copy it to another byte slice. Use the `Txn.Delete()` method to delete a key. ## Monotonically increasing integers To get unique monotonically increasing integers with strong durability, you can use the `DB.GetSequence` method. This method returns a `Sequence` object, which is thread-safe and can be used concurrently via various goroutines. Badger would lease a range of integers to hand out from memory, with the bandwidth provided to `DB.GetSequence`. The frequency at which disk writes are done is determined by this lease bandwidth and the frequency of `Next` invocations. Setting a bandwidth too low would do more disk writes, setting it too high would result in wasted integers if Badger is closed or crashes. To avoid wasted integers, call `Release` before closing Badger. ```go seq, err := db.GetSequence(key, 1000) defer seq.Release() for { num, err := seq.Next() } ``` ## Merge operations Badger provides support for ordered merge operations. You can define a func of type `MergeFunc` which takes in an existing value, and a value to be *merged* with it. It returns a new value which is the result of the *merge* operation. All values are specified in byte arrays. For e.g., here is a merge function (`add`) which appends a `[]byte` value to an existing `[]byte` value. ```go // Merge function to append one byte slice to another func add(originalValue, newValue []byte) []byte { return append(originalValue, newValue...) } ``` This function can then be passed to the `DB.GetMergeOperator()` method, along with a key, and a duration value. The duration specifies how often the merge function is run on values that have been added using the `MergeOperator.Add()` method. `MergeOperator.Get()` method can be used to retrieve the cumulative value of the key associated with the merge operation. ```go key := []byte("merge") m := db.GetMergeOperator(key, add, 200*time.Millisecond) defer m.Stop() m.Add([]byte("A")) m.Add([]byte("B")) m.Add([]byte("C")) res, _ := m.Get() // res should have value ABC encoded ``` Example: merge operator which increments a counter ```go func uint64ToBytes(i uint64) []byte { var buf [8]byte binary.BigEndian.PutUint64(buf[:], i) return buf[:] } func bytesToUint64(b []byte) uint64 { return binary.BigEndian.Uint64(b) } // Merge function to add two uint64 numbers func add(existing, new []byte) []byte { return uint64ToBytes(bytesToUint64(existing) + bytesToUint64(new)) } ``` It can be used as ```go key := []byte("merge") m := db.GetMergeOperator(key, add, 200*time.Millisecond) defer m.Stop() m.Add(uint64ToBytes(1)) m.Add(uint64ToBytes(2)) m.Add(uint64ToBytes(3)) res, _ := m.Get() // res should have value 6 encoded ``` ## Setting time to live (TTL) and user metadata on keys Badger allows setting an optional Time to Live (TTL) value on keys. Once the TTL has elapsed, the key is no longer retrievable and is eligible for garbage collection. A TTL can be set as a `time.Duration` value using the `Entry.WithTTL()` and `Txn.SetEntry()` API methods. ```go err := db.Update(func(txn *badger.Txn) error { e := badger.NewEntry([]byte("answer"), []byte("42")).WithTTL(time.Hour) err := txn.SetEntry(e) return err }) ``` An optional user metadata value can be set on each key. A user metadata value is represented by a single byte. It can be used to set certain bits along with the key to aid in interpreting or decoding the key-value pair. User metadata can be set using `Entry.WithMeta()` and `Txn.SetEntry()` API methods. ```go err := db.Update(func(txn *badger.Txn) error { e := badger.NewEntry([]byte("answer"), []byte("42")).WithMeta(byte(1)) err := txn.SetEntry(e) return err }) ``` `Entry` APIs can be used to add the user metadata and TTL for same key. This `Entry` then can be set using `Txn.SetEntry()`. ```go err := db.Update(func(txn *badger.Txn) error { e := badger.NewEntry([]byte("answer"), []byte("42")).WithMeta(byte(1)).WithTTL(time.Hour) err := txn.SetEntry(e) return err }) ``` ## Iterating over keys To iterate over keys, we can use an `Iterator`, which can be obtained using the `Txn.NewIterator()` method. Iteration happens in byte-wise lexicographical sorting order. ```go err := db.View(func(txn *badger.Txn) error { opts := badger.DefaultIteratorOptions opts.PrefetchSize = 10 it := txn.NewIterator(opts) defer it.Close() for it.Rewind(); it.Valid(); it.Next() { item := it.Item() k := item.Key() err := item.Value(func(v []byte) error { fmt.Printf("key=%s, value=%s\n", k, v) return nil }) if err != nil { return err } } return nil }) ``` The iterator allows you to move to a specific point in the list of keys and move forward or backward through the keys one at a time. By default, Badger prefetches the values of the next 100 items. You can adjust that with the `IteratorOptions.PrefetchSize` field. However, setting it to a value higher than `GOMAXPROCS` (which we recommend to be 128 or higher) shouldn’t give any additional benefits. You can also turn off the fetching of values altogether. See section below on key-only iteration. ### Prefix scans To iterate over a key prefix, you can combine `Seek()` and `ValidForPrefix()`: ```go db.View(func(txn *badger.Txn) error { it := txn.NewIterator(badger.DefaultIteratorOptions) defer it.Close() prefix := []byte("1234") for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { item := it.Item() k := item.Key() err := item.Value(func(v []byte) error { fmt.Printf("key=%s, value=%s\n", k, v) return nil }) if err != nil { return err } } return nil }) ``` ### Possible pagination implementation using Prefix scans Considering that iteration happens in **byte-wise lexicographical sorting** order, it's possible to create a sorting-sensitive key. For example, a simple blog post key might look like:`feed:userUuid:timestamp:postUuid`. Here, the `timestamp` part of the key is treated as an attribute, and items will be stored in the corresponding order: | Order ASC | Key | | :-------: | :------------------------------------------------------------ | | 1 | feed:tQpnEDVRoCxTFQDvyQEzdo:1733127889:tQpnEDVRoCxTFQDvyQEzdo | | 2 | feed:tQpnEDVRoCxTFQDvyQEzdo:1733127533:1Mryrou1xoekEaxzrFiHwL | | 3 | feed:tQpnEDVRoCxTFQDvyQEzdo:1733127486:pprRrNL2WP4yfVXsSNBSx6 | It's important to properly configure keys for lexicographical sorting to avoid incorrect ordering. A **prefix scan** through the preceding keys can be achieved using the prefix `feed:tQpnEDVRoCxTFQDvyQEzdo`. All matching keys are returned, sorted by `timestamp`.\ Sorting can be done in ascending or descending order based on `timestamp` or `reversed timestamp` as needed: ```go reversedTimestamp := math.MaxInt64-time.Now().Unix() ``` This makes it possible to implement simple pagination by using a limit for the number of keys and a cursor (the last key from the previous iteration) to identify where to resume. ```go // startCursor may look like 'feed:tQpnEDVRoCxTFQDvyQEzdo:1733127486'. // A prefix scan with this cursor will locate the specific key where // the previous iteration stopped. err = db.badger.View(func(txn *badger.Txn) error { it := txn.NewIterator(opts) defer it.Close() // Prefix example 'feed:tQpnEDVRoCxTFQDvyQEzdo' // if no cursor provided prefix scan starts from the beginning p := prefix if startCursor != nil { p = startCursor } iterNum := 0 // Tracks the number of iterations to enforce the limit. for it.Seek(p); it.ValidForPrefix(p); it.Next() { // The method it.ValidForPrefix ensures that iteration continues // as long as keys match the prefix. // For example, if p = 'feed:tQpnEDVRoCxTFQDvyQEzdo:1733127486', // it matches keys like // 'feed:tQpnEDVRoCxTFQDvyQEzdo:1733127889:pprRrNL2WP4yfVXsSNBSx6'. // Once the starting point for iteration is found, revert the prefix // back to 'feed:tQpnEDVRoCxTFQDvyQEzdo' to continue iterating sequentially. // Otherwise, iteration would stop after a single prefix-key match. p = prefix item := it.Item() key := string(item.Key()) if iterNum > limit { // Limit reached. nextCursor = key // Save the next cursor for future iterations. return nil } iterNum++ // Increment iteration count. err := item.Value(func(v []byte) error { fmt.Printf("key=%s, value=%s\n", k, v) return nil }) if err != nil { return err } } // If the number of iterations is less than the limit, // it means there are no more items for the prefix. if iterNum < limit { nextCursor = "" } return nil }) return nextCursor, err ``` ### Key-only iteration Badger supports a unique mode of iteration called *key-only* iteration. It's several order of magnitudes faster than regular iteration, because it involves access to the LSM-tree only, which is usually resident entirely in RAM. To enable key-only iteration, you need to set the `IteratorOptions.PrefetchValues` field to `false`. This can also be used to do sparse reads for selected keys during an iteration, by calling `item.Value()` only when required. ```go err := db.View(func(txn *badger.Txn) error { opts := badger.DefaultIteratorOptions opts.PrefetchValues = false it := txn.NewIterator(opts) defer it.Close() for it.Rewind(); it.Valid(); it.Next() { item := it.Item() k := item.Key() fmt.Printf("key=%s\n", k) } return nil }) ``` ## Stream Badger provides a Stream framework, which concurrently iterates over all or a portion of the DB, converting data into custom key-values, and streams it out serially to be sent over network, written to disk, or even written back to Badger. This is a lot faster way to iterate over Badger than using a single Iterator. Stream supports Badger in both managed and normal mode. Stream uses the natural boundaries created by SSTables within the LSM tree, to quickly generate key ranges. Each goroutine then picks a range and runs an iterator to iterate over it. Each iterator iterates over all versions of values and is created from the same transaction, thus working over a snapshot of the DB. Every time a new key is encountered, it calls `ChooseKey(item)`, followed by `KeyToList(key, itr)`. This allows a user to select or reject that key, and if selected, convert the value versions into custom key-values. The goroutine batches up 4 MB worth of key-values, before sending it over to a channel. Another goroutine further batches up data from this channel using *smart batching* algorithm and calls `Send` serially. This framework is designed for high throughput key-value iteration, spreading the work of iteration across many goroutines. `DB.Backup` uses this framework to provide full and incremental backups quickly. Dgraph is a heavy user of this framework. In fact, this framework was developed and used within Dgraph, before getting ported over to Badger. ```go stream := db.NewStream() // db.NewStreamAt(readTs) for managed mode. // -- Optional settings stream.NumGo = 16 // Set number of goroutines to use for iteration. stream.Prefix = []byte("some-prefix") // Leave nil for iteration over the whole DB. stream.LogPrefix = "Badger.Streaming" // For identifying stream logs. Outputs to Logger. // ChooseKey is called concurrently for every key. If left nil, assumes true by default. stream.ChooseKey = func(item *badger.Item) bool { return bytes.HasSuffix(item.Key(), []byte("er")) } // KeyToList is called concurrently for chosen keys. This can be used to convert // Badger data into custom key-values. If nil, uses stream.ToList, a default // implementation, which picks all valid key-values. stream.KeyToList = nil // -- End of optional settings. // Send is called serially, while Stream.Orchestrate is running. stream.Send = func(list *pb.KVList) error { return proto.MarshalText(w, list) // Write to w. } // Run the stream if err := stream.Orchestrate(context.Background()); err != nil { return err } // Done. ``` ## Garbage collection Badger values need to be garbage collected, because of two reasons: * Badger keeps values separately from the LSM tree. This means that the compaction operations that clean up the LSM tree do not touch the values at all. Values need to be cleaned up separately. * Concurrent read/write transactions could leave behind multiple values for a single key, because they're stored with different versions. These could accumulate, and take up unneeded space beyond the time these older versions are needed. Badger relies on the client to perform garbage collection at a time of their choosing. It provides the following method, which can be invoked at an appropriate time: * `DB.RunValueLogGC()`: This method is designed to do garbage collection while Badger is online. Along with randomly picking a file, it uses statistics generated by the LSM-tree compactions to pick files that are likely to lead to maximum space reclamation. It's recommended to be called during periods of low activity in your system, or periodically. One call would only result in removal of at max one log file. As an optimization, you could also immediately re-run it whenever it returns nil error (indicating a successful value log GC), as shown below. ```go ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() for range ticker.C { again: err := db.RunValueLogGC(0.7) if err == nil { goto again } } ``` * `DB.PurgeOlderVersions()`: This method is **DEPRECATED** since v1.5.0. Now, Badger's LSM tree automatically discards older/invalid versions of keys. The `RunValueLogGC` method would not garbage collect the latest value log. ## Database backup There are two public API methods `DB.Backup()` and `DB.Load()` which can be used to do online backups and restores. Badger v0.9 provides a CLI tool `badger`, which can do offline backup/restore. Make sure you have `$GOPATH/bin` in your PATH to use this tool. The command below creates a version-agnostic backup of the database, to a file `badger.bak` in the current working directory ```sh badger backup --dir ``` To restore `badger.bak` in the current working directory to a new database: ```sh badger restore --dir ``` See `badger --help` for more details. If you have a Badger database that was created using v0.8 (or below), you can use the `badger_backup` tool provided in v0.8.1, and then restore it using the preceding command to upgrade your database to work with the latest version. ```sh badger_backup --dir --backup-file badger.bak ``` We recommend all users to use the `Backup` and `Restore` APIs and tools. However, Badger is also rsync-friendly because all files are immutable, barring the latest value log which is append-only. So, rsync can be used as rudimentary way to perform a backup. In the following script, we repeat rsync to ensure that the LSM tree remains consistent with the MANIFEST file while doing a full backup. ```sh #!/bin/bash set -o history set -o histexpand # Makes a complete copy of a Badger database directory. # Repeat rsync if the MANIFEST and SSTables are updated. rsync -avz --delete db/ dst while !! | grep -q "(MANIFEST\|\.sst)$"; do :; done ``` ## Memory usage Badger's memory usage can be managed by tweaking several options available in the `Options` struct that's passed in when opening the database using `DB.Open`. * Number of memtables (`Options.NumMemtables`) * If you modify `Options.NumMemtables`, also adjust `Options.NumLevelZeroTables` and `Options.NumLevelZeroTablesStall` accordingly. * Number of concurrent compactions (`Options.NumCompactors`) * Size of table (`Options.BaseTableSize`) * Size of value log file (`Options.ValueLogFileSize`) If you want to decrease the memory usage of Badger instance, tweak these options (ideally one at a time) until you achieve the desired memory usage. # Troubleshooting Source: https://docs.hypermode.com/badger/troubleshooting Common issues and solutions with Badger ## Writes are getting stuck **Update: with the new `Value(func(v []byte))` API, this deadlock can no longer happen.** The following is true for users on Badger v1.x. This can happen if a long running iteration with `Prefetch` is set to false, but an `Item::Value` call is made internally in the loop. That causes Badger to acquire read locks over the value log files to avoid value log GC removing the file from underneath. As a side effect, this also blocks a new value log GC file from being created, when the value log file boundary is hit. Please see GitHub issues [#293](https://github.com/hypermodeinc/badger/issues/293) and [#315](https://github.com/hypermodeinc/badger/issues/315). There are multiple workarounds during iteration: 1. Use `Item::ValueCopy` instead of `Item::Value` when retrieving value. 2. Set `Prefetch` to true. Badger would then copy over the value and release the file lock immediately. 3. When `Prefetch` is false, don't call `Item::Value` and do a pure key-only iteration. This might be useful if you just want to delete a lot of keys. 4. Do the writes in a separate transaction after the reads. ## Writes are really slow Are you creating a new transaction for every single key update, and waiting for it to `Commit` fully before creating a new one? This leads to very low throughput. We've created `WriteBatch` API which provides a way to batch up many updates into a single transaction and `Commit` that transaction using callbacks to avoid blocking. This amortizes the cost of a transaction really well, and provides the most efficient way to do bulk writes. ```go wb := db.NewWriteBatch() defer wb.Cancel() for i := 0; i < N; i++ { err := wb.Set(key(i), value(i), 0) // Will create txns as needed. handle(err) } handle(wb.Flush()) // Wait for all txns to finish. ``` Note that `WriteBatch` API doesn't allow any reads. For read-modify-write workloads, you should be using the `Transaction` API. ## I don't see any disk writes If you're using Badger with `SyncWrites=false`, then your writes might not be written to value log and won't get synced to disk immediately. Writes to LSM tree are done inmemory first, before they get compacted to disk. The compaction would only happen once `BaseTableSize` has been reached. So, if you're doing a few writes and then checking, you might not see anything on disk. Once you `Close` the database, you'll see these writes on disk. ## Reverse iteration doesn't produce the right results Just like forward iteration goes to the first key which is equal or greater than the SEEK key, reverse iteration goes to the first key which is equal or lesser than the SEEK key. Therefore, SEEK key would not be part of the results. You can typically add a `0xff` byte as a suffix to the SEEK key to include it in the results. See the following issues: [#436](https://github.com/hypermodeinc/badger/issues/436) and [#347](https://github.com/hypermodeinc/badger/issues/347). ## Which instances should I use for Badger? We recommend using instances which provide local SSD storage, without any limit on the maximum IOPS. In AWS, these are storage optimized instances like i3. They provide local SSDs which clock 100K IOPS over 4KB blocks easily. ## I'm getting a closed channel error ```sh panic: close of closed channel panic: send on closed channel ``` If you're seeing panics like above, this would be because you're operating on a closed DB. This can happen, if you call `Close()` before sending a write, or multiple times. You should ensure that you only call `Close()` once, and all your read/write operations finish before closing. ## Are there any Go specific settings that I should use? We *highly* recommend setting a high number for `GOMAXPROCS`, which allows Go to observe the full IOPS throughput provided by modern SSDs. In Dgraph, we have set it to 128. For more details, [see this thread](https://groups.google.com/d/topic/golang-nuts/jPb_h3TvlKE/discussion). ## Are there any Linux specific settings that I should use? We recommend setting `max file descriptors` to a high number depending upon the expected size of your data. On Linux and Mac, you can check the file descriptor limit with `ulimit -n -H` for the hard limit and `ulimit -n -S` for the soft limit. A soft limit of `65535` is a good lower bound. You can adjust the limit as needed. ## I see "manifest has unsupported version: X (we support Y)" error This error means you have a badger directory which was created by an older version of badger and you're trying to open in a newer version of badger. The underlying data format can change across badger versions and users have to migrate their data directory. Badger data can be migrated from version X of badger to version Y of badger by following the steps listed below. Assume you were on badger v1.6.0 and you wish to migrate to v2.0.0 version. 1. Install badger version v1.6.0 * `cd $GOPATH/src/github.com/dgraph-io/badger` * `git checkout v1.6.0` * `cd badger && go install` This should install the old badger binary in your `$GOBIN`. 2. Create Backup * `badger backup --dir path/to/badger/directory -f badger.backup` 3. Install badger version v2.0.0 * `cd $GOPATH/src/github.com/dgraph-io/badger` * `git checkout v2.0.0` * `cd badger && go install` This should install the new badger binary in your `$GOBIN`. 4. Restore data from backup * `badger restore --dir path/to/new/badger/directory -f badger.backup` This creates a new directory on `path/to/new/badger/directory` and add badger data in newer format to it. NOTE - The preceding steps shouldn't cause any data loss but please ensure the new data is valid before deleting the old badger directory. ## Why do I need gcc to build badger? Does badger need Cgo? Badger doesn't directly use Cgo but it relies on [https://github.com/DataDog/zstd](https://github.com/DataDog/zstd) library for zstd compression and the library requires [`gcc/cgo`](https://pkg.go.dev/cmd/cgo). You can build Badger without Cgo by running `CGO_ENABLED=0 go build`. This builds Badger without the support for ZSTD compression algorithm. As of Badger versions [v2.2007.4](https://github.com/hypermodeinc/badger/releases/tag/v2.2007.4) and [v3.2103.1](https://github.com/hypermodeinc/badger/releases/tag/v3.2103.1) the DataDog ZSTD library was replaced by pure Golang version and Cgo is no longer required. The new library is [backwards compatible in nearly all cases](https://discuss.dgraph.io/t/use-pure-go-zstd-implementation/8670/10): Yes they're compatible both ways. The only exception is 0 bytes of input which gives 0 bytes output with the Go zstd. But you already have the zstd.WithZeroFrames(true) which will wrap 0 bytes in a header so it can be fed to DD zstd. This is only relevant when downgrading. # Community and Support Source: https://docs.hypermode.com/community-and-support Get help with Hypermode and connect with other developers Whether you're just getting started or you're a seasoned developer, we're here to help you every step of the way. We’re excited to have you as part of the Hypermode community and look forward to seeing what you build! ## Community [Discord](https://discord.hypermode.com) is our main forum where you can ask questions, share your knowledge, and connect with other developers. ## Getting help If you encounter a bug or have a feature request, you can open an issue on the relevant GitHub repository: * [Modus](https://github.com/hypermodeinc/modus/issues) * [Hyp CLI](https://github.com/hypermodeinc/hyp-cli/issues) * [Badger](https://github.com/hypermodeinc/badger/issues) All paid Hypermode packages include commercial support. Customers can reach out via the Hypermode Console or through email at [help@hypermode.com](mailto:help@hypermode.com). ## Stay connected Stay up-to-date with the latest news, updates, and announcements from Hypermode: * **[X](https://x.com/hypermodeinc)**: follow us on X for the latest news and updates * **[Blog](https://hypermode.com/blog)**: explore our blog for in-depth articles, tutorials, and case studies # Configure Environment Source: https://docs.hypermode.com/configure-environment Define environment parameters for your app On your first deployment, and when you add new connections thereafter, you may need to configure your environment for your app to be ready for traffic. ## Connection secrets If you included parameters for Connection Secrets in your [app manifest](/modus/app-manifest), you'll need to add the parameter values in the Hypermode Console. From your project home, navigate to **Settings** → **Connections**. Add the values for your defined connection authentication parameters. ## Model hosting Where available, Hypermode defaults to shared model instances. Refer to the [Hosted Models](/hosted-models) documentation for instructions on setting up hosted models. ## Scaling your runtime resources We're working to make runtime scaling self-service. In the meantime, reach out at [help@hypermode.com](mailto:help@hypermode.com) for assistance with this request. # Create Project Source: https://docs.hypermode.com/create-project Initialize your Modus app with Hypermode A Hypermode project represents your Modus app and associated models. You can create a project through the Hypermode Console or Hyp CLI. ## Create with Hypermode Console Hypermode relies on Git as a source for project deployment. Through the project creation flow, you'll connect Hypermode to your GitHub account. From your organization home, click **New Project**. Set a name for the project and click **Create**. Once you have created a project, Hypermode prompts you to finish setting up your project using the CLI. First, install the Modus CLI and initialize your app. For more information on creating your first Modus app, visit the [Modus quickstart](modus/quickstart). Next, initialize the app with Hypermode through the [Hyp CLI](/hyp-cli) and link your GitHub repo with your Modus app to Hypermode using: ```bash hyp link ``` This command adds a default GitHub Actions workflow to build your Modus app and a Hypermode GitHub app for auto-deployment. Once initialized, each commit to the target branch triggers a deployment. You can also connect to an existing GitHub repository. The last step is triggering your first deployment. If you linked your project through the Hyp CLI, make sure to push your changes first to trigger a deployment. # Deploy Project Source: https://docs.hypermode.com/deploy A git-based flow for simple deployment Hypermode features a native GitHub integration for the deployment of Hypermode projects. The deployment includes your Modus app as well as any models defined in the [app manifest](/modus/app-manifest). Preview environments for live validation of pull requests are in development. ## Link your project to GitHub After you push your Modus app to GitHub, you can link your Hypermode project to the repo through the Hyp CLI. ```bash hyp link ``` ## Build When you link your project with Hypermode, the Hyp CLI adds a GitHub Actions workflow to your repo that builds your Modus app to Hypermode on commit. ## Deploy On successful build of your project, Hypermode automatically deploys your project changes. For [hosted models](/hosted-models), Hypermode creates a connection for your app to a shared or dedicated model instance. You can view the deployment status from the Deployments tab within the Hypermode Console. # Hosted Models Source: https://docs.hypermode.com/hosted-models Iterate quickly with seamless access to the most popular models Hypermode includes a set of shared models available for integration into your app on a pay-per-token basis. Need a bespoke model? You can include a model from [Hugging Face](https://huggingface.co/) in your [app manifest](/modus/app-manifest) and Hypermode runs and manages it for you. ## Setup To use a Hypermode-hosted model, set `connection: "hypermode"`, `provider: "hugging-face"`, and set `sourceModel` to be the model name as specified on Hugging Face. ```json modus.json {3-7} { ... "models": { "text-generator": { "sourceModel": "meta-llama/Llama-3.2-3B-Instruct", "provider": "hugging-face", "connection": "hypermode" } } ... } ``` ## Deployment mode We run our most popular models as multi-tenant, shared instances across projects and customers. By default, if the model you use is available as a shared model, your app uses these shared models at runtime. If the model you use isn't available as a shared model, Hypermode automatically spins up a dedicated instance of the model for your project. ## Shared models These are the models available currently with shared instances: * [`meta-llama/Llama-3.2-3B-Instruct`](https://huggingface.co/meta-llama/Llama-3.2-3B-Instruct) * [`deepseek-ai/DeepSeek-R1-Distill-Llama-8B`](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-8B) * [`sentence-transformers/all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) * [`AntoineMC/distilbart-mnli-github-issues`](https://huggingface.co/AntoineMC/distilbart-mnli-github-issues) * [`distilbert/distilbert-base-uncased-finetuned-sst-2-english`](https://huggingface.co/distilbert/distilbert-base-uncased-finetuned-sst-2-english) We're constantly evaluating model usage in determining new models to add to our shared catalog. Interested in consuming an open source model not listed here by the token? Let us know at [help@hypermode.com](mailto:help@hypermode.com). # Hyp CLI Source: https://docs.hypermode.com/hyp-cli Comprehensive reference for the Hyp CLI commands and usage Hyp CLI is a command-line tool for managing your Hypermode account and projects. When using Hyp CLI alongside the Modus CLI, users get access to [Hypermode-hosted models](/hosted-models). ## Install Install Hyp CLI via npm. ```bash npm install -g @hypermode/hyp-cli ``` ## Commands ### `login` Log in to your Hypermode account. When executed, this command redirects to the Hypermode website to authenticate. It stores an authentication token locally and prompts you to select your organization context. ### `logout` Log out of your Hypermode account. This command clears your local authentication token. ### `org switch` Switch to a different org context within the CLI session. # Integrate API Source: https://docs.hypermode.com/integrate-api Easily add intelligent features to your app Hypermode makes it easy to incrementally add intelligence your app. ## API endpoint You can find your project's API endpoint in the Hypermode Console, in your project's Home tab, in the format `https://.hypermode.app/`. ## API token Hypermode protects your project's endpoint with an API key. In your project dashboard, navigate to **Settings** → **API Keys** to find and manage your API tokens. From your app, you can call the API by passing the API token in the `Authorization` header. Here's an example using the `fetch` API in JavaScript: ```javascript const endpoint = "" // your API endpoint const key = "" // your API key // your graphql query const query = `query { ... }` const response = await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json", Authorization: key, }, body: JSON.stringify({ query: query, }), }) const data = await response.json() ``` Additional authorization methods are under development. If your app requires a different protocol, reach out at [help@hypermode.com](mailto:help@hypermode.com). # Introduction Source: https://docs.hypermode.com/introduction Build Intelligent APIs. {/* vale Google.Contractions = NO */} ## What is Hypermode? {/* vale Google.Contractions = YES */} Hypermode is a managed service that provides the workbench and infrastructure to create **powerful, engaging, and secure AI assistants, APIs, and backend services**. With every push to Git, Hypermode automatically builds and deploys your functions. It creates a live, scalable API for you to preview, test, and promote to production. ## What's included with Hypermode? Hypermode provides a suite of tools and services to help you build and manage your intelligent APIs. Use one component or all of them together as the complete backend for your app. Modus is an open source, serverless framework for building functions and APIs, powered by WebAssembly Experiment rapidly with access to the most popular and newest models. Dgraph is a distributed, transactional graph database optimized for knowledge graph creation and management End-to-end observability and control for your functions, models, and data. Hypermode's console showing project, model, and API management. # Modify Organization Source: https://docs.hypermode.com/modify-organization Update organization attributes or delete your organization Organizations contain a set of related projects and members. This forms the billing envelope for Hypermode. You can modify your organization's name and slug through the settings. ## Update organization name Your organization name is a display name only and changing it doesn't impact your running apps. To update your organization display name, click your organization's name in the top navigation. Then, click **Settings** → **General**. Enter a new name and click **Save**. ## Update organization slug From your organization home view, click **Settings** → **General**. Enter a new slug and click **Save**. When you update the organization slug, Hypermode reflects the new slug in your app endpoints across all projects. You must update any existing integrations that use the existing app endpoints. Make sure to communicate these changes with your team or stakeholders. ## Delete organization If you no longer need an organization, you can permanently delete it. To delete an organization, you must [delete all projects](/modify-project#delete-project) first. To delete an organization, click your organization's name in the top navigation. Then, click **Settings** → **Delete**. Click **Delete** and enter the organization's name as confirmation. This action is irreversible and permanently deletes your organization. # Modify Project Source: https://docs.hypermode.com/modify-project Update project attributes or delete your project After you create a project, you can modify its name and slug through the project settings. ## Update project name Your project name is display-only and changing it doesn't impact your running app. To update your project display name, click **Settings** → **General**. Enter a new name and click **Save**. ## Update project slug From your project view, click **Settings** → **General**. Enter a new slug and click **Save**. If you update the project slug, you must update any existing integrations that use the existing app endpoints. Make sure to communicate these changes with your team or stakeholders. ## Delete project If you no longer need a project, you can permanently delete it. Deleting a project removes all associated endpoints, configurations, and data. To delete a project, from the project in the console, click **Settings** → **Delete**. Click **Delete** and enter the project’s name as confirmation. This action is irreversible and permanently deletes all project data and configuration. # API Generation Source: https://docs.hypermode.com/modus/api-generation Create the signature for your API Modus automatically creates an external API based on the endpoints defined in your [app manifest](/modus/app-manifest#endpoints). Modus generates the API signature based on the functions you export from your app. ## Exporting functions Modus uses the default conventions for each language. Functions written in Go use starting capital letters to expose functions as public. Modus creates an external API for public functions from any file that belongs to the `main` package. The functions below generate an API endpoint with the signature ```graphql type Query { classifyText(text: String!, threshold: Float!): String! } ``` Since the `classify` function isn't capitalized, Modus doesn't include it in the generated GraphQL API. ```go package main import ( "errors" "fmt" "github.com/hypermodeAI/functions-go/pkg/models" "github.com/hypermodeAI/functions-go/pkg/models/experimental" ) const modelName = "my-classifier" // this function takes input text and a probability threshold, and returns the // classification label determined by the model, if the confidence is above the // threshold; otherwise, it returns an empty string func ClassifyText(text string, threshold float32) (string, error) { predictions, err:= classify(text) if err != nil { return "", err } prediction := predictions[0] if prediction.Confidence < threshold { return "", nil } return prediction.Label, nil } func classify(texts ...string) ([]experimental.ClassifierResult, error) { model, err := models.GetModel[experimental.ClassificationModel](modelName) if err != nil { return nil, err } input, err := model.CreateInput(texts...) if err != nil { return nil, err } output, err := model.Invoke(input) if err != nil { return nil, err } if len(output.Predictions) != len(texts) { word := "prediction" if len(texts) > 1 { word += "s" } return nil, fmt.Errorf("expected %d %s, got %d", len(texts), word, len(output.Predictions)) } return output.Predictions, nil } ``` Functions written in AssemblyScript use ES module-style `import` and `export` statements. With the default package configuration, Modus creates an external API for functions exported form the `index.ts` file located in the `functions/assembly` folder of your project. The functions below generate an API endpoint with the signature ```graphql type Query { classifyText(text: String!, threshold: Float!): String! } ``` Since the `classify` function isn't exported from the module, Modus doesn't include it in the generated GraphQL API. ```ts import { models } from "@hypermode/modus-sdk-as" import { ClassificationModel, ClassifierResult, } from "@hypermode/modus-sdk-as/models/experimental/classification" const modelName: string = "my-classifier" // this function takes input text and a probability threshold, and returns the // classification label determined by the model, if the confidence is above the // threshold; otherwise, it returns an empty string export function classifyText(text: string, threshold: f32): string { const predictions = classify(text, threshold) const prediction = predictions[0] if (prediction.confidence < threshold) { return "" } return prediction.label } function classify(text: string, threshold: f32): ClassifierResult[] { const model = models.getModel(modelName) const input = model.createInput([text]) const output = model.invoke(input) return output.predictions } ``` # App Manifest Source: https://docs.hypermode.com/modus/app-manifest Define the resources for your app The manifest for your Modus app allows you to configure the exposure and resources for your functions at runtime. You define the manifest in the `modus.json` file within the root of directory of your app. ## Structure Expose your functions for integration into your frontend or federated API Establish connectivity for external endpoints and model hosts Define inference services for use in your functions Define sets of text data to enable natural language search ### Base manifest A simple manifest, which exposes a single GraphQL endpoint with a bearer token for authentication, looks like this: ```json modus.json { "$schema": "https://schema.hypermode.com/modus.json", "endpoints": { "default": { "type": "graphql", "path": "/graphql", "auth": "bearer-token" } } } ``` ## Endpoints Endpoints make your functions available outside of your Modus app. The `endpoints` object in the app manifest allows you to define these endpoints for integration into your frontend or federated API. Each endpoint requires a unique name, specified as a key containing only alphanumeric characters and hyphens. Only a GraphQL endpoint is available currently, but the modular design of Modus allows for the introduction of additional endpoint types in the future. ### GraphQL endpoint This endpoint type supports the GraphQL protocol to communicate with external clients. You can use a GraphQL client, such as [urql](https://github.com/urql-graphql/urql) or [Apollo Client](https://github.com/apollographql/apollo-client), to interact with the endpoint. **Example:** ```json modus.json { "endpoints": { "default": { "type": "graphql", "path": "/graphql", "auth": "bearer-token" } } } ``` Always set to `"graphql"` for this endpoint type. The path for the endpoint. Must start with a forward slash `/`. The authentication method for the endpoint. Options are `"bearer-token"` or `"none"`. See [Authentication](/modus/authentication) for additional details. ## Connections Connections establish connectivity and access to external services. They're used for HTTP and GraphQL APIs, database connections, and externally hosted AI models. The `connections` object in the app manifest allows you to define these hosts, for secure access from within a function. Each connection requires a unique name, specified as a key containing only alphanumeric characters and hyphens. Each connection has a `type` property, which controls how it's used and which additional properties are available. The following table lists the available connection types: | Type | Purpose | Function Classes | | :----------- | :------------------------------- | :-------------------------- | | `http` | Connect to an HTTP(S) web server | `http`, `graphql`, `models` | | `dgraph` | Connect to a Dgraph database | `dgraph` | | `mysql` | Connect to a MySQL database | `mysql` | | `neo4j` | Connect to a Neo4j database | `neo4j` | | `postgresql` | Connect to a PostgreSQL database | `postgresql` | **Don't include secrets directly in the manifest!** If your connection requires authentication, you can include *placeholders* in connection properties which resolve to their respective secrets at runtime. When developing locally, [set secrets using environment variables](/modus/run-locally#environment-secrets). When deployed on Hypermode, set the actual secrets via the Hypermode Console, where they're securely stored until needed. ### HTTP connection This connection type supports the HTTP and HTTPS protocols to communicate with external hosts. You can use the [HTTP APIs](/modus/sdk/assemblyscript/http) in the Modus SDK to interact with the host. This connection type is also used for [GraphQL APIs](/modus/sdk/assemblyscript/graphql) and to invoke externally hosted AI [models](/modus/sdk/assemblyscript/models). **Example:** ```json modus.json { "connections": { "openai": { "type": "http", "baseUrl": "https://api.openai.com/", "headers": { "Authorization": "Bearer {{API_KEY}}" } } } } ``` Always set to `"http"` for this connection type. Base URL for connections to the host. Must end with a trailing slash and may contain path segments if necessary. Example: `"https://api.example.com/v1/"` Full URL endpoint for connections to the host. Example: `"https://models.example.com/v1/classifier"` You must include either a `baseUrl` or an `endpoint`, but not both. * Use `baseUrl` for connections to a host with a common base URL. * Use `endpoint` for connections to a specific URL. Typically, you'll use the `baseUrl` field. However, some APIs, such as `graphql.execute`, require the full URL in the `endpoint` field. If provided, requests on the connection include these headers. Each key-value pair is a header name and value. Values may include variables using the `{{VARIABLE}}` template syntax, which resolve at runtime to secrets provided for each connection, via the Hypermode Console. This example specifies a header named `Authorization` that uses the `Bearer` scheme. A secret named `AUTH_TOKEN` provides the token: ```json "headers": { "Authorization": "Bearer {{AUTH_TOKEN}}" } ``` This example specifies a header named `X-API-Key` provided by a secret named `API_KEY`: ```json "headers": { "X-API-Key": "{{API_KEY}}" } ``` You can use a special syntax for connections that require [HTTP basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication). In this example, secrets named `USERNAME` and `PASSWORD` combined and then are base64-encoded to form a compliant `Authorization` header value: ```json "headers": { "Authorization": "Basic {{base64(USERNAME:PASSWORD)}}" } ``` If provided, requests on the connection include these query parameters, appended to the URL. Each key-value pair is a parameter name and value. Values may include variables using the `{{VARIABLE}}` template syntax, which resolve at runtime to secrets provided for each connection, via the Hypermode Console. This example specifies a query parameter named `key` provided by a secret named `API_KEY`: ```json "queryParameters": { "key": "{{API_KEY}}" } ``` ### Dgraph connection This connection type supports connecting to Dgraph databases. You can use the [Dgraph APIs](/modus/sdk/assemblyscript/dgraph) in the Modus SDK to interact with the database. **Example:** ```json modus.json { "connections": { "my-dgraph": { "type": "dgraph", "grpcTarget": "frozen-mango.grpc.eu-central-1.aws.cloud.dgraph.io:443", "key": "{{DGRAPH_API_KEY}}" } } } ``` Always set to `"dgraph"` for this connection type. The gRPC target for the Dgraph database. The API key for the Dgraph database. ### MySQL connection This connection type supports connecting to MySQL databases. You can use the [MySQL APIs](/modus/sdk/assemblyscript/mysql) in the Modus SDK to interact with the database. **Example:** ```json modus.json { "connections": { "my-database": { "type": "mysql", "connString": "mysql://{{USERNAME}}:{{PASSWORD}}@db.example.com:3306/dbname?tls=true" } } } ``` Always set to `"mysql"` for this connection type. The connection string for the MySQL database. Values may include variables using the `{{VARIABLE}}` template syntax, which resolve at runtime to secrets provided for each connection, via the Hypermode Console. The connection string in the preceding example includes: * A username and password provided by secrets named `USERNAME` & `PASSWORD` * A host named `db.example.com` on port `3306` * A database named `dbname` * Encryption enabled via `tls=true` - which is highly recommended for secure connections Set the connection string using a URI format [as described in the MySQL documentation](https://dev.mysql.com/doc/refman/8.4/en/connecting-using-uri-or-key-value-pairs.html#connecting-using-uri). However, any optional parameters provided should be in the form specified by the Go MySQL driver used by the Modus Runtime, [as described here](https://github.com/go-sql-driver/mysql/blob/master/README.md#parameters) For example, use `tls=true` to enable encryption (not `sslmode=require`). ### Neo4j connection This connection type supports connecting to Neo4j databases. You can use the [Neo4j APIs](/modus/sdk/assemblyscript/neo4j) in the Modus SDK to interact with the database. **Example:** ```json modus.json { "connections": { "my-neo4j": { "type": "neo4j", "dbUri": "bolt://localhost:7687", "username": "neo4j", "password": "{{NEO4J_PASSWORD}}" } } } ``` Always set to `"neo4j"` for this connection type. The URI for the Neo4j database. The username for the Neo4j database. The password for the Neo4j database. ### PostgreSQL connection This connection type supports connecting to PostgreSQL databases. You can use the [PostgreSQL APIs](/modus/sdk/assemblyscript/postgresql) in the Modus SDK to interact with the database. **Example:** ```json modus.json { "connections": { "my-database": { "type": "postgresql", "connString": "postgresql://{{PG_USER}}:{{PG_PASSWORD}}@db.example.com:5432/data?sslmode=require" } } } ``` Always set to `"postgresql"` for this connection type. The connection string for the PostgreSQL database. Values may include variables using the `{{VARIABLE}}` template syntax, which resolve at runtime to secrets provided for each connection, via the Hypermode Console. The connection string in the preceding example includes: * A username and password provided by secrets named `PG_USER` & `PG_PASSWORD` * A host named `db.example.com` on port `5432` * A database named `data` * SSL mode set to `require` - which is highly recommended for secure connections Refer to [the PostgreSQL documentation](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING) for more details on connection strings. Managed PostgreSQL providers often provide a pre-made connection string for you to copy. Check your provider's documentation for details. For example, if using Neon, refer to the [Neon documentation](https://neon.tech/docs/connect/connect-from-any-app). See [Running locally with secrets](/modus/run-locally#environment-secrets) for more details on how to set secrets for local development. ## Models AI models are a core resource for inferencing. The `models` object in the app manifest allows you to easily define models, whether hosted by Hypermode or another host. Each model requires a unique name, specified as a key, containing only alphanumeric characters and hyphens. ```json modus.json { "models": { "text-generator": { "sourceModel": "meta-llama/Llama-3.2-3B-Instruct", "provider": "hugging-face", "connection": "hypermode" } } } ``` Original relative path of the model within the provider's repository. Source provider of the model. If the `connection` value is `hypermode`, this field is mandatory. `hugging-face` is currently the only supported option. Connection for the model instance. * Specify `"hypermode"` for models that [Hypermode hosts](/hosted-models). * Otherwise, specify a name that matches a connection defined in the [`connections`](#connections) section of the manifest. When using `hugging-face` as the `provider` and `hypermode` as the `connection`, Hypermode automatically facilitates the connection to an instance of a shared or dedicated instance of the model. Your project's functions securely access the hosted model, with no further configuration required. For more details, see [hosted models](/hosted-models). ## Collections Collections simplify the usage of vector embeddings to build natural language search features. The `collections` object allows you to define indexed data types that are automatically embedded and searchable based on the search method you define. Each collection requires a unique name, specified as a key, containing only alphanumeric characters and hyphens. For more detail on implementing Collections, see [Search](/modus/search). ```json modus.json { "collections": { "myProducts": { "searchMethods": { "searchMethod": { "embedder": "myEmbedder", "index": { "type": "sequential" } } } } } } ``` Search methods define a pair of an embedder and index to make available for searching the data in your collection. The function name to embed text added to the collection. If provided, describes the index mechanism used by the search method. `type`: specifies the type of the index. For example, `sequential` (default). # Authentication Source: https://docs.hypermode.com/modus/authentication Protect your API It's easy to secure your Modus app with authentication. Modus currently supports bearer token authentication, with additional authentication methods coming soon. ## Bearer tokens Modus supports authentication via the `Authorization` header in HTTP requests. You can use the `Authorization` header to pass a bearer JSON Web Token (JWT) to your Modus app. The token authenticates the user and authorize access to resources. To use bearer token authentication for your Modus app, be sure to set the `auth` property on your endpoint to `"bearer-token"` in your [app manifest](/modus/app-manifest#endpoints). ### Setting verification keys Once set, Modus verifies tokens passed in the `Authorization` header of incoming requests against the public keys you provide. To enable this verification, you must pass the public keys using the `MODUS_PEMS` or `MODUS_JWKS_ENDPOINTS` environment variable. The value of the `MODUS_PEMS` or `MODUS_JWKS_ENDPOINTS` environment variable should be a JSON object with the public keys as key-value pairs. This is an example of how to set the `MODUS_PEMS` and `MODUS_JWKS_ENDPOINTS` environment variable: ```bash MODUS_PEMS export MODUS_PEMS='{\"key1\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwJ9z1z1z1z1z1z\\n-----END PUBLIC KEY-----\"}' ``` ```bash MODUS_JWKS_ENDPOINTS export MODUS_JWKS_ENDPOINTS='{"my-auth-provider":"https://myauthprovider.com/application/o/myappname/.wellknown/jwks.json"}' ``` When deploying your Modus app on Hypermode, the bearer token authentication is automatically set up. ### Verifying tokens To verify the token, Modus uses the public keys passed via the `MODUS_PEMS` environment variable. If the token is verifiable with any of the verification keys provided, Modus decodes the JWT token and passes the decoded claims as an environment variable. ### Accessing claims The decoded claims are available through the `auth` API in the Modus SDK. To access the decoded claims, use the `getJWTClaims()` function. The function allows the user to pass in a class to deserialize the claims into, and returns an instance of the class with the claims. This allows users to access the claims in the token and use them to authenticate and authorize users in their Modus app. ```go Go import github.com/hypermodeinc/modus/sdk/go/pkg/auth type ExampleClaims struct { Sub string `json:"sub"` Exp int64 `json:"exp"` Iat int64 `json:"iat"` } func GetClaims() (*ExampleClaims, error) { return auth.GetJWTClaims[*ExampleClaims]() } ``` ```ts AssemblyScript import { auth } from "@hypermode/modus-sdk-as" @json export class ExampleClaims { public sub!: string public exp!: i64 public iat!: i64 } export function getClaims(): ExampleClaims { return auth.getJWTClaims() } ``` # Changelog Source: https://docs.hypermode.com/modus/changelog The latest changes and improvements in Modus Welcome to the Modus changelog! Here you'll find the latest improvements to the Modus framework. Stay informed about what's new and what's changed to make the most out of Modus. Here's a short summary of the larger items shipped with each major or minor release. For a more detailed list of changes, please refer to [the full change log in GitHub](https://github.com/hypermodeinc/modus/blob/main/CHANGELOG.md). ## Version history | Version | Date | Description | | ------- | ---------- | ------------------------------------------------------------ | | 0.17.x | 2025-01-24 | MySQL support, local model tracing, and OpenAI improvements. | | 0.16.x | 2024-12-23 | Local time and time zone support | | 0.15.x | 2024-12-13 | Neo4j support | | 0.14.x | 2024-11-23 | Modus API Explorer + in-code documentation | | 0.13.x | 2024-10-17 | First release of Modus as an open source framework 🎉 | Stay tuned for more updates and improvements as we continue to enhance Modus! # Data Fetching Source: https://docs.hypermode.com/modus/data-fetching Pull data into your app Modus makes it simple to fetch data from external sources. The specific data source you're retrieving from determines the method you use to interact with it. ## Fetching from databases ### PostgreSQL PostgreSQL is a powerful, open source relational database system. Modus provides a simple way to interact with PostgreSQL databases with the `postgresql` APIs. Here is an example of fetching a person from a PostgreSQL database using the Modus SDK: ```go Go package main import ( "github.com/hypermodeinc/modus/sdk/go/pkg/postgresql" ) // the name of the PostgreSQL connection, as specified in the modus.json manifest const connection = "my-database" type Person struct { Name string `json:"name"` Age int `json:"age"` } func GetPerson(name string) (*Person, error) { const query = "select * from persons where name = $1" rows, _, _ := postgresql.Query[Person](connection, query, name) return &rows[0], nil } ``` ```ts AssemblyScript import { postgresql } from "@hypermode/modus-sdk-as" // the name of the PostgreSQL connection, as specified in the modus.json manifest const connection = "my-database" @json class Person { name!: string age!: i32 } export function getPerson(name: string): Person { const query = "select * from persons where name = $1" const params = new postgresql.Params() params.push(name) const response = postgresql.query(connection, query, params) return response.rows[0] } ``` ### Dgraph Dgraph is a distributed, transactional graph database. Modus offers an easy way to query and mutate data in Dgraph with the `dgraph` APIs. Here is an example of fetching a person from a Dgraph database using the Modus SDK: ```go Go package main import ( "encoding/json" "github.com/hypermodeinc/modus/sdk/go/pkg/dgraph" ) // the name of the Dgraph connection, as specified in the modus.json manifest const connection = "my-dgraph" // declare structures used to parse the JSON document returned by Dgraph query. type Person struct { Name string `json:"name,omitempty"` Age int32 `json:"age,omitempty"` } // Dgraph returns an array of Persons type GetPersonResponse struct { Persons []*Person `json:"persons"` } func GetPerson(name string) (*Person, error) { statement := `query getPerson($name: string!) { persons(func: eq(name, $name)) { name age } } ` variables := map[string]string{ "$name": name, } response, _ := dgraph.Execute(connection, &dgraph.Request{ Query: &dgraph.Query{ Query: statement, Variables: variables, }, }) var data GetPersonResponse json.Unmarshal([]byte(response.Json), &data) return data.Persons[0], nil } ``` ```ts AssemblyScript import { dgraph } from "@hypermode/modus-sdk-as" import { JSON } from "json-as" // the name of the Dgraph connection, as specified in the modus.json manifest const connection: string = "my-dgraph" // declare classes used to parse the JSON document returned by Dgraph query. @json class Person { name: string = "" age: i32 = 0 } // Dgraph returns an array of objects @json class GetPersonResponse { persons: Person[] = [] } export function getPerson(name: string): Person { const statement = ` query getPerson($name: string) { persons(func: eq(name, $name)) { name age } }` const vars = new dgraph.Variables() vars.set("$name", name) const resp = dgraph.execute( connection, new dgraph.Request(new dgraph.Query(statement, vars)), ) const persons = JSON.parse(resp.Json).persons return persons[0] } ``` ### Neo4j Neo4j is a graph database management system. Modus provides a simple way to query and mutate data in Neo4j with the `neo4j` APIs. Here is an example of mutating & fetching a person from a Neo4j database using the Modus SDK: ```go Go package main import ( "github.com/hypermodeinc/modus/sdk/go/pkg/neo4j" ) const host = "my-database" func CreatePeopleAndRelationships() (string, error) { people := []map[string]any{ {"name": "Alice", "age": 42, "friends": []string{"Bob", "Peter", "Anna"}}, {"name": "Bob", "age": 19}, {"name": "Peter", "age": 50}, {"name": "Anna", "age": 30}, } for _, person := range people { _, err := neo4j.ExecuteQuery(host, "MERGE (p:Person {name: $person.name, age: $person.age})", map[string]any{"person": person}) if err != nil { return "", err } } for _, person := range people { if person["friends"] != "" { _, err := neo4j.ExecuteQuery(host, ` MATCH (p:Person {name: $person.name}) UNWIND $person.friends AS friend_name MATCH (friend:Person {name: friend_name}) MERGE (p)-[:KNOWS]->(friend) `, map[string]any{ "person": person, }) if err != nil { return "", err } } } return "People and relationships created successfully", nil } type Person struct { Name string `json:"name"` Age int64 `json:"age"` } func GetAliceFriendsUnder40() ([]Person, error) { response, err := neo4j.ExecuteQuery(host, ` MATCH (p:Person {name: $name})-[:KNOWS]-(friend:Person) WHERE friend.age < $age RETURN friend `, map[string]any{ "name": "Alice", "age": 40, }, neo4j.WithDbName("neo4j"), ) if err != nil { return nil, err } nodeRecords := make([]Person, len(response.Records)) for i, record := range response.Records { node, _ := neo4j.GetRecordValue[neo4j.Node](record, "friend") name, err := neo4j.GetProperty[string](&node, "name") if err != nil { return nil, err } age, err := neo4j.GetProperty[int64](&node, "age") if err != nil { return nil, err } nodeRecords[i] = Person{ Name: name, Age: age, } } return nodeRecords, nil } ``` ```ts AssemblyScript import { neo4j } from "@hypermode/modus-sdk-as" // This host name should match one defined in the modus.json manifest file. const hostName: string = "my-database" @json class Person { name: string age: i32 friends: string[] | null constructor(name: string, age: i32, friends: string[] | null = null) { this.name = name this.age = age this.friends = friends } } export function CreatePeopleAndRelationships(): string { const people: Person[] = [ new Person("Alice", 42, ["Bob", "Peter", "Anna"]), new Person("Bob", 19), new Person("Peter", 50), new Person("Anna", 30), ] for (let i = 0; i < people.length; i++) { const createPersonQuery = ` MATCH (p:Person {name: $person.name}) UNWIND $person.friends AS friend_name MATCH (friend:Person {name: friend_name}) MERGE (p)-[:KNOWS]->(friend) ` const peopleVars = new neo4j.Variables() peopleVars.set("person", people[i]) const result = neo4j.executeQuery(hostName, createPersonQuery, peopleVars) if (!result) { throw new Error("Error creating person.") } } return "People and relationships created successfully" } export function GetAliceFriendsUnder40(): Person[] { const vars = new neo4j.Variables() vars.set("name", "Alice") vars.set("age", 40) const query = ` MATCH (p:Person {name: $name})-[:KNOWS]-(friend:Person) WHERE friend.age < $age RETURN friend ` const result = neo4j.executeQuery(hostName, query, vars) if (!result) { throw new Error("Error getting friends.") } const personNodes: Person[] = [] for (let i = 0; i < result.Records.length; i++) { const record = result.Records[i] const node = record.getValue("friend") const person = new Person( node.getProperty("name"), node.getProperty("age"), ) personNodes.push(person) } return personNodes } ``` ## Fetching from APIs ### HTTP HTTP protocols underpin RESTful APIs with OpenAPI schemas. Modus provides a convenient way to interact with any external HTTP API using the `http` APIs in the Modus SDK. Here is an example of fetching a person from an HTTP API using the Modus SDK: ```go Go package main import ( "encoding/json" "fmt" "github.com/hypermodeinc/modus/sdk/go/pkg/http" ) // declare structures used to parse the JSON document returned by the REST API type Person struct { Name string `json:"name,omitempty"` Age int32 `json:"age,omitempty"` } func GetPerson(name string) (*Person, error) { url := fmt.Sprintf("https://example.com/api/person?name=%s", name) response, _ := http.Fetch(url) // The API returns Person object as JSON var person Person response.JSON(&person) return person, nil } ``` ```ts AssemblyScript import { http } from "@hypermode/modus-sdk-as" @json class Person { name: string = "" age: i32 = 0 } export function getPerson(name: string): Person { const url = `https://example.com/api/people?name=${name}` const response = http.fetch(url) // the API returns Person object as JSON return response.json() } ``` ### GraphQL GraphQL is a data-centric query language for APIs that allows you to fetch only the data you need. With the `graphql` APIs in the Modus SDK, you can easily fetch data from any GraphQL endpoint. Here is an example of fetching a person from a GraphQL API using the Modus SDK: ```go Go import ( "encoding/json" "github.com/hypermodeinc/modus/sdk/go/pkg/graphql" ) // the name of the GraphQL connection, as specified in the modus.json manifest const connection = "my-graphql-api" // declare structures used to parse the JSON document returned type Person struct { Name string `json:"name,omitempty"` Age int32 `json:"age,omitempty"` } type GetPersonResponse struct { Person *Person `json:"getPerson"` } func GetPerson(name string) (*Person, error) { statement := `query getPerson($name: String!) { getPerson(name: $name) { age name } }` vars := map[string]any{ "name": name, } response, _ := graphql.Execute[GetPersonResponse](connection, statement, vars) return response.Data.Person, nil } ``` ```ts AssemblyScript import { graphql } from "@hypermode/modus-sdk-as" // the name of the GraphQL connection, as specified in the modus.json manifest const connection: string = "my-graphql-api" // declare classes used to parse the JSON document returned @json class Person { name: string = "" age: i32 = 0 } @json class GetPersonResponse { getPerson: Person | null } export function getPerson(name: string): Person | null { const statement = ` query getPerson($name: String!) { getPerson(name: $name) { age name } }` const vars = new graphql.Variables() vars.set("name", name) const response = graphql.execute( connection, statement, vars, ) return response.data!.getPerson } ``` # Using DeepSeek Source: https://docs.hypermode.com/modus/deepseek-model Use the DeepSeek-R1 Model with your Modus app `DeepSeek-R1` is an open source AI reasoning model that rivals the performance of frontier models such as OpenAI's o1 in complex reasoning tasks like math and coding. Benefits of DeepSeek include: * **Performance**: `DeepSeek-R1` achieves comparable results to OpenAI's o1 model on several benchmarks. * **Efficiency**: The model uses significantly fewer parameters and therefore operates at a lower cost relative to competing frontier models. * **Open Source**: The open source license allows both commercial and non-commercial usage of the model weights and associated code. * **Novel training approach**: The research team developed DeepSeek-R1 through a multi-stage approach that combines reinforcement learning, fine-tuning, and data distillation. * **Distilled versions**: The DeepSeek team released smaller, distilled models based on DeepSeek-R1 that offer high reasoning capabilities with fewer parameters. In this guide we review how to use the `DeepSeek-R1` model in your Modus app. ## Options for using DeepSeek with Modus There are two options for using `DeepSeek-R1` in your Modus app: 1. [Use the distilled `DeepSeek-R1` model hosted by Hypermode](#using-the-distilled-deepseek-model-hosted-by-hypermode) Hypermode hosts and makes available the distilled DeepSeek model based on `Llama-3.1-8B` enabling Modus apps to use it in both local development environments and deployed applications. 2. [Use the DeepSeek Platform API with your Modus app](#using-the-deepseek-platform-api-with-modus) Access DeepSeek models hosted on the DeepSeek platform by configuring a DeepSeek connection in your Modus app and using your DeepSeek API key ## Using the distilled DeepSeek model hosted by Hypermode The open source `DeepSeek-R1-Distill-Llama-8B` DeepSeek model is available on Hypermode as a [shared model](/hosted-models#shared-models). This means that we can invoke this model in a Modus app in both a local development environment and also in an app deployed on Hypermode. The `DeepSeek-R1-Distill-Llama-8B` model is a distilled version of the DeepSeek-R1 model which has been fine-tuned using the `Llama-3.1-8B` model as a base model, using samples generated by DeepSeek-R1. Distilled models offer similar high reasoning capabilities with fewer parameters.