Skip to content

Instantly share code, notes, and snippets.

@jeffijoe
Last active December 21, 2022 07:06
Show Gist options
  • Save jeffijoe/8a88b8f22992b2d56e2bc7b70d0d9333 to your computer and use it in GitHub Desktop.
Save jeffijoe/8a88b8f22992b2d56e2bc7b70d0d9333 to your computer and use it in GitHub Desktop.
Awilix example for @garbagemule
// middleware/auth.js
export default async function authenticate (ctx, next) {
// Do your auth stuff here.
const decoded = extractTokenSomehow(ctx)
const teamId = decoded.teamId
const userId = decoded.userId
// Use the User Repository and Team Repository (or a cache?) to fetch
// our entities.
// Resolve instances of these using Awilix's cradle proxy.
const { userRepository, teamRepository, context } = ctx.container.cradle
const [user, team] = await Promise.all([
userRepository.get(userId),
teamRepository.get(teamId)
])
// Authenticate our context
context.authenticate(team, user)
return await next()
}
// domain/context.js
export default class Context {
team = null
user = null
// Easy way to throw if we are not authenticated.
check () {
if (!this.user) {
// Handle this totally transport-agnostic error in a Koa error handler.
throw new errors.Unauthenticated()
}
}
authenticate (team, user) {
this.team = team
this.user = user
}
}
// routes.js
import { makeInvoker } from 'awilix-koa'
import authenticate from './middleware/auth'
function makeAPI ({ userService }) {
return {
findUsers: (ctx) =>
// One-liner - just call the appropriate service
userService.findUsers(ctx.request.query)
}
}
export default function setupRoutes (router) {
const api = makeInvoker(makeAPI)
router.get(
'/users',
authenticate, // authenticate middleware first
api('findUsers') // calls the findUsers() function and injects dependencies
)
}
// server.js
import Koa from 'koa'
import KoaRouter from 'koa-router'
import { createContainer, Lifetime } from 'awilix'
import { scopePerRequest } from 'awilix-koa'
import setupRoutes from './routes'
function createServer () {
const app = new Koa()
const router = new KoaRouter()
// Create our DI container.
const container = createContainer()
// Load modules
.loadModules([
['services/*.js', Lifetime.SCOPED], // services are to be "per request"
['repositories/*.js', Lifetime.SINGLETON] // repos need no context, so reuse single instances
])
// Register our "context" as singleton, too
.registerClass({
// Lifetime.SCOPED = one instance *per request*
context: [require('./domain/context').default, Lifetime.SCOPED]
})
// Add the scopePerRequest middleware,
// this is what enables the "per request" instancing.
app.use(scopePerRequest(container))
app.use(router.routes())
setupRoutes(router)
return app
}
// Ready to roll
createServer().listen(1337, () => console.log('Good to go!'))
// repositories/userRepository.js
export default class UserRepository {
find (query) {
// Do whatever you gotta do!
return Promise.resolve([
{ id: 1, name: 'Jeff Hansen' }
])
}
}
// services/userService.js
import omit from 'lodash/omit'
export default function createUserService ({ userRepository, context }) {
return {
async getAllUsers (query) {
context.check() // require authentication
query = {
...query,
// We have a context
teamId: context.team.id
}
const users = await userRepository.find(query) // data store don't care about context
if (context.user.isAdmin) {
return users
}
// If we are not an admin, strip out contact info
return users.map(user => omit(user, ['email']))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment