Resources Overview
Nitric provides cloud-native building blocks that make it simple to declare the resources you need as part of your application code.
The Nitric deployment engine will run through your code at deployment time, interpreting resources that are declared as part of your application and creating them in the cloud you are pushing to.
Rules
There are a few rules to keep in mind when declaring Nitric resources as part of your application.
Don't declare resources in runtime code
Nitric needs to be aware of resources at deployment time so they can be deployed appropriately. Declaring resources at runtime means the resource won't be declared when deploying your application. Consequently, the resource will not be provisioned to the cloud.
The Nitric deployment engine does not evaluate runtime code at deployment time as this could result in unintended behavior or side effects.
A working example:
import { api, bucket } from '@nitric/sdk'
// ✅ This declaration will work
const files = bucket('files').allow('read')
api('public').get('/files/:name', (ctx) => {
// ❌ This declaration will not work, as this is only called at runtime.
const badBucket = bucket('wont-work').allow('read')
})
Don't use runtime methods in top level code
Calling runtime methods in top-level code can lead to unintended side effects. Nitric resources should be accessed in a deterministic way to avoid unintentional side-effects.
A working example:
import { api, bucket } from '@nitric/sdk'
const files = bucket('files').allow('read')
// ❌ This access will not work.
const fileContents = files.file('example.txt').read()
api('public').get('/files/:name', (ctx) => {
// ✅ This access will work.
const fileContents = files.file('example.txt').read()
})
If you want to limit resource access to a single instance/access consider implementing a lazy singleton.
import { api, bucket } from '@nitric/sdk'
const files = bucket('files').allow('read')
// 👀 Singleton value
let fileContents: string
// Lazy access method
const getFileContents = async () => {
if (!fileContents) {
fileContents = await files.file('example.txt').read()
}
return fileContents
}
api('public').get('/files/:name', (ctx) => {
const fileContents = await getFileContents()
})
Best Practices
✅ Re-use declarations for shared resources
When many services share a resource it's helpful to re-use resource declaration like any other variable in your code.
For example:
// lib/resources.ts
import { api, topic } from '@nitric/sdk'
export const publicApi = api('public')
export const updateTopic = topic('updates')
// services/api.ts
import { publicApi, updateTopic } from '../lib/resources'
const publisher = updateTopic.allow('publish')
publicApi.post('/update', async (ctx) => {
await publisher.publish({ test: 'message' })
})
// services/updates.ts
import { updateTopic } from '../lib/resources'
updateTopic.subscribe((ctx) => {
console.log('got the message')
})
Sharing resources like this can avoid nasty typos, and allows easily shared references to a single resource using your IDE.
❌ Avoid declaring permissions for shared resources
Creating resource permissions in the same context as the resources can make those permissions leak. This is demonstrated in the below example:
// lib/resources.ts
import { api, bucket } from '@nitric/sdk'
export const publicApi = api('public')
export const bucketOne = bucket('bucket-one').allow('read')
export const bucketTwo = bucket('bucket-two').allow('read')
// services/service-one.ts
import { publicApi, bucketOne } from '../lib/resources'
publicApi.get('bucket-one/file/:name', (ctx) => {
// do something with the file
})
// services/service-two.ts
import { publicApi, bucketTwo } from '../lib/resources'
publicApi.get('bucket-two/file/:name', (ctx) => {
// do something with the file
})
In this scenario, both service-one
and service-two
have read access to both buckets, even though each service is only using one of the buckets. This is because they are declared in the same context and evaluated at the same time in each of the services.
Resource permissions should always be declared in the scope of a single service unless the permissions required by one or more services are identical.
This practice can break principal of least privilege in infrastructure deployments