Using Holo Services
Holo services are the database, models, storage, queues, events, mail, cache, and config. You use them by importing them in server-side code: routes, server actions, jobs, listeners, and CLI commands. There's no container to resolve and no app object to pass around.
Quick example
import cache from '@holo-js/cache'
import { useConfig } from '@holo-js/config'
import { DB } from '@holo-js/db'
import Post from '@/server/models/Post'
export async function GET() {
const recentPosts = await Post.latest().limit(5).get()
const totals = await cache.remember('dashboard.totals', 60, async () => ({
posts: await DB.table('posts').count(),
users: await DB.table('users').count(),
}))
return Response.json({
app: useConfig('app.name'),
recentPosts,
totals,
})
}Your framework's Holo integration loads your config and connects each service before your code runs. In a worker or CLI command, Holo does the same when the process starts.
WARNING
Holo services run on the server only. Don't import models, DB, or storage from client components or browser code.
Where to import each service
| Service | Import | Guide |
|---|---|---|
| Config | import { config, useConfig } from '@holo-js/config' | Configuration |
| Database | import { DB } from '@holo-js/db' | Database |
| Models | import Post from '<root>/server/models/Post' | ORM |
| Storage | import { Storage } from '@holo-js/storage/runtime' | File storage |
| Media | Methods on media models, such as post.addMedia(file) | Media library |
| Queue | Job definitions, or import { dispatch } from '@holo-js/queue' | Queues |
| Events | Event definitions, or import { Event } from '@holo-js/events' | Events |
import { sendMail } from '@holo-js/mail' | ||
| Notifications | import { notify } from '@holo-js/notifications' | Notifications |
| Cache | import cache from '@holo-js/cache' | Cache |
| Broadcasting | import { broadcast } from '@holo-js/broadcast' | Broadcasting |
| Current user | import auth, { user } from '@holo-js/auth' | Authentication |
| Sessions | import session from '@holo-js/session' | Sessions |
Read config
Use useConfig() to read a whole config file or one value with a dot path. config() does the same thing for a single value:
import { config, useConfig } from '@holo-js/config'
const app = useConfig('app')
const appName = useConfig('app.name')
const mailgunSecret = config('services.mailgun.secret')Both are typed from your config/*.ts files, so paths autocomplete and values have the right type.
Query the database
Use models when you work with records, and the DB facade for direct table queries:
import { DB } from '@holo-js/db'
import Post from '../models/Post'
const posts = await Post.with('author').latest().paginate(20)
const activeUsers = await DB.table('users')
.where('active', true)
.orderBy('name')
.get()Store files
Storage writes to the default disk. Use Storage.disk() for a named disk from config/storage.ts:
import { Storage } from '@holo-js/storage/runtime'
await Storage.disk('public').put('avatars/user-1.png', avatarBytes)
const report = await Storage.get('reports/daily.txt')Use the media library instead when a file belongs to a model:
await post.addMedia(image).toMediaCollection('images')
const thumbnailUrl = await post.getFirstMediaUrl('images', 'thumb')Run work later
Dispatch a job to run it on a queue worker:
import IndexBlogPost from '../jobs/blog/index-post'
await IndexBlogPost.dispatch({ action: 'created', postId: post.id })
await IndexBlogPost.dispatch({ action: 'updated', postId: post.id })
.onQueue('search')
.delay(30)Use dispatchSync() when the job must run right now, in the current process.
Dispatch an event when one action should trigger several reactions. Each listener decides whether it runs immediately or on the queue:
import BlogPostSaved from '../events/blog/post-saved'
await BlogPostSaved.dispatch({
action: 'created',
postId: post.id,
title: post.title,
status: post.status,
slug: post.slug,
})Use services in jobs and listeners
Jobs and listeners can use every service. The queue worker loads your project before it runs the first job:
import { defineJob } from '@holo-js/queue'
import Post from '../../models/Post'
export default defineJob<{ postId: number }>({
async handle(payload) {
const post = await Post.find(payload.postId)
return { indexed: post !== undefined }
},
})A job has no request, so there's no current user, session, or cookies. Pass the IDs you need in the payload and load the records inside the job. Payloads must be plain JSON values.
Use services in custom commands
In a custom command, wrap code that uses Holo services in context.withRuntime(). It loads your project, keeps it open while your callback runs, and shuts it down afterwards:
import { defineCommand } from '@holo-js/cli'
import Post from '../../models/Post'
export default defineCommand({
description: 'Reindex all published posts.',
async run(context) {
const count = await context.withRuntime(async () => {
const posts = await Post.where('status', 'published').get()
return posts.length
})
console.log(`Reindexed ${count} posts.`)
},
})npx holo posts:reindexAsync context rules
Holo tracks some state per async call chain rather than globally. That keeps overlapping requests from seeing each other's data. In practice:
Transactions follow your code. Inside a DB.transaction() callback, every model and DB query uses that transaction, including queries in functions you call from the callback. You don't need to pass the transaction around:
import { DB } from '@holo-js/db'
import Order from '../models/Order'
import Product from '../models/Product'
await DB.transaction(async () => {
const order = await Order.create({ user_id: userId, total: 4999 })
await reserveStock(productId, 1)
return order
})
async function reserveStock(productId: number, quantity: number) {
const product = await Product.findOrFail(productId)
await product.update({ stock: product.stock - quantity })
}Await everything inside the callback. The transaction commits when the callback's promise settles. A query you start but don't await can run after the commit, outside the transaction.
Run side effects after commit. To send mail or dispatch jobs only once the data is saved, run them after DB.transaction() returns, or register them with DB.afterCommit(). Events can do this for you; see after-commit events.
The current user belongs to the request. user(), sessions, and CSRF checks read the current request. They work in route handlers, server actions, load functions, and form actions. They don't work in jobs, commands, or code that runs after the response is sent.
Available functions
Config
Data and files
Background work
Events and messages
Caching, auth, and sessions
config()
Returns one config value by dot path. Defined in @holo-js/config. See Read config.
useConfig()
Returns a whole config file or one value by dot path. Defined in @holo-js/config. See Read config.
DB
The database facade for table queries, imported from @holo-js/db. See query builder.
const count = await DB.table('posts').count()Storage
Stores and reads files, imported from @holo-js/storage/runtime. See file storage.
defineJob()
Defines a background job, imported from @holo-js/queue. See queues.
dispatch()
Queues a job for a worker to run later, imported from @holo-js/queue. See Run work later.
dispatchSync()
Runs a job now and returns its result instead of queueing it, imported from @holo-js/queue. See queues.
defineCommand()
Defines a custom holo command, imported from @holo-js/cli. See custom commands.
Event
The event facade, imported from @holo-js/events. See events.
dispatchEvent()
Announces an event to its listeners, imported from @holo-js/events. See events.
sendMail()
Sends an email, imported from @holo-js/mail. See mail.
notify()
Sends a notification to a notifiable, such as a user, imported from @holo-js/notifications. See notifications.
broadcast()
Sends a real-time message to a channel, imported from @holo-js/broadcast. See broadcasting.
cache
The cache facade, the default export of @holo-js/cache. See cache.
user()
Returns the signed-in user, or null when nobody is signed in, imported from @holo-js/auth. See authentication.
session
The session facade, the default export of @holo-js/session. See sessions.