# Dependency injection with crystal macros

**URL:** <https://forum.crystal-lang.org/t/dependency-injection-with-crystal-macros/8684>\
**Category:** Community\
**Created:** [January 31, 2026, 7:15am UTC](https://forum.crystal-lang.org/t/dependency-injection-with-crystal-macros/8684 "2026-01-31T07:15:34Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![willhbr](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/willhbr/32/3078_2.png) [@willhbr](https://forum.crystal-lang.org/u/willhbr)\
**Post date:** [January 31, 2026, 7:15am UTC](https://forum.crystal-lang.org/t/dependency-injection-with-crystal-macros/8684/1 "2026-01-31T07:15:34Z")

</div>

I just wrote up a blog post on how I implemented a dependency injection framework using Crystal macros:

> **[Building Dependency Injection with Crystal Macros — Will Richardson](https://willhbr.net/2026/01/31/crystal-dependency-injection/)**
>
> When I first came across dependency injection I was a sceptic. Surely we could just create objects the normal way instead of worrying about modules and bindings? Eventually though I realised how you’re actually just generating the boilerplate code...

You can skip straight to the code:

> **[geode/src/geode/dependency\_injector.cr at main](https://codeberg.org/willhbr/geode/src/branch/main/src/geode/dependency_injector.cr)**
>
> geode - Helpful additional functionality for Crystal

I think it differs a little from other projects that I’ve seen in that I’m following the model of [Dagger](https://dagger.dev/) (which I’ve used for Android development) and so it supports arbitrarily nested scopes to control where certain classes can be accessed.

Doing this with macros is neat because it _should_ mean that there’s no runtime overhead to looking up the existing objects.

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [January 31, 2026, 3:35pm UTC](https://forum.crystal-lang.org/t/dependency-injection-with-crystal-macros/8684/2 "2026-01-31T15:35:53Z")

</div>

This was a good read! I know there aren’t many DI implementations in Crystal, so was interesting to see how someone else went about implementing it. I have a few thoughts/questions:

> The next trick is to use the unsafe `.allocate` method to grab some uninitialised memory where we can put our object. We then just call the right `initialize` method (as defined by `method` ) which will set the instance variables. That’s just a matter of generating a method call from the information in `method` :

Is there a reason you have to use `.allocate`? If the `Inject` annotation is always applied to the initialize method, it should be safe to just do `{{ @type }}.new(...)` and let Crystal itself find that overload?

> Since Crystal macros don’t have methods, every time we want to access the specially-named getter, we have to duplicate that snippet.

I feel you 😭, would love to get more feedback on/move forward with [RFC 0018: Macro Methods by Blacksmoke16 · Pull Request #18 · crystal-lang/rfcs · GitHub](https://github.com/crystal-lang/rfcs/pull/18) as a way to DRY things up a bit. I found `/[^\w]/` to be a bit more robust as well.

> For subscopes that correspond to a particular action (like an HTTP request) you need to be able to put values into the dependency graph.

This feels odd to me (assuming it’s not just for example purposes). Like would it not be better to _not_ have the server context be in the DI container, but passed to a related service as an argument? E.g. `def process(context : HTTP::Server::Context)`? The scoping concept is neat but I think I’d have to see some more real-world use cases to fully understand its benefit.

---

<div class="post-metadata">

**Author:** ![willhbr](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/willhbr/32/3078_2.png) [@willhbr](https://forum.crystal-lang.org/u/willhbr)\
**Post date:** [January 31, 2026, 10:31pm UTC](https://forum.crystal-lang.org/t/dependency-injection-with-crystal-macros/8684/3 "2026-01-31T22:31:46Z")

</div>

You’re right about `.allocate`, I totally could have just called `.new`. The way it’s implemented currently means you _can_ have a method that’s not called `initialize` do the initialisation, but that doesn’t play nice with the uninitialised variable checker, so I should just require it be `initialize` and call `new`.

It’s hard to give a good example to demonstrate why scopes are useful, as for small applications they mostly just add complexity. The [Dagger documentation on subcomponents](https://dagger.dev/dev-guide/subcomponents) might be interesting, that’s where I’ve lifted the idea from.

The advantages get more noticeable the deeper your dependency graph grows. Let’s say I added authentication to this HTTP server and now each request will come from a particular user. Without a scope, I’d have to plumb this value through the entire call stack to where it’s needed. With a scope, I can just make a `User` available in the dependency graph for all requests, and anyone that needs it can just inject it, the layers in-between don’t have to be changed.

I guess an HTTP server in Crystal is a poor example, since what you’d do for request-specific information would be to patch `HTTP::Server::Context` to hold the information I needed and forget about any dep injection. In languages without patching and macros you’d be subclassing and writing this boilerplate by hand, which is terrible and why dependency injection is used.

For example in an Android app if you have some object that needs to be shared within an activity (ie a screen of the app) you’d probably just hold a reference to it in your subclass of `Activity`, then anyone can grab the current activity, cast it, and read the field. Dagger/Hilt just auto-generate this boilerplate.

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [February 1, 2026, 12:03am UTC](https://forum.crystal-lang.org/t/dependency-injection-with-crystal-macros/8684/4 "2026-02-01T00:03:33Z")

</div>

> [@willhbr](#):
>
> The way it’s implemented currently means you _can_ have a method that’s not called `#initialize` do the initialisation

Could set it up to just call whatever class method it’s applied to. That would handle custom “factory”-like constructors as well. E.g. `.from_dsn` or whatever. If it’s applied to `initialize`, then fallback to `.new`, or possibly even if there is no `Inject` annotation at all.

Hmm yea, maybe it just stems from a diff architecture for a different primary use case?

> [@willhbr](#):
>
> With a scope, I can just make a `User` available in the dependency graph for all requests, and anyone that needs it can just inject it, the layers in-between don’t have to be changed.

The way I’m more accustomed to handling this is leveraging the mutable state of the services. E.g. at some point in the request/response life-cycle, after performing the authentication check, you set the `User` instance to some service. From here other downstream services can inject your “UserStore” service or whatever and access the current user if one is defined. From here you could pass it around as an argument, to additional services if needed. This way the DI container consists just of actual dependencies and not also runtime values.

This is essentially what I did for integrating the [Athena DI](https://athenaframework.org/why_athena/#dependency-injection) component. Each request has its own container scoped to the fiber its running in, so it’s free to mutate things as needed without leaking, or having to reset state, between requests.

---

<div class="post-metadata">

**Author:** ![willhbr](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/willhbr/32/3078_2.png) [@willhbr](https://forum.crystal-lang.org/u/willhbr)\
**Post date:** [February 1, 2026, 3:16am UTC](https://forum.crystal-lang.org/t/dependency-injection-with-crystal-macros/8684/5 "2026-02-01T03:16:59Z")

</div>

> Each request has its own container scoped to the fiber its running in, so it’s free to mutate things as needed without leaking, or having to reset state, between requests.

That makes sense, I guess the difference here is that you can inject stuff from the global scope as well as from the local scope.

> Could set it up to just call whatever class method it’s applied to.

Ooh that’s a nice idea, since the `.new` method will inherit the annotations from the `initialize` method so it’ll just work.
