DEV Community

new web framework

genix on February 28, 2025

Hey everyone! I’m still working hard on Gland, and after a lot of refactoring and R&D, I’m excited to share some of the new developments. Gl...
Collapse
 
keyru_nasirusman profile image
keyru Nasir Usman • Edited

Do we really need another new web framework?

Collapse
 
m__mdy__m profile image
genix

It really depends on what you’re looking for. Express still "works," and NestJS still "works," so if those fit your needs, there's no reason to switch.

But whether we need a new framework or not comes down to what problems you're trying to solve. Gland isn't trying to replace NestJS it’s taking a different, fully event-driven approach that makes code more modular and flexible. If that resonates with your project’s needs, then it’s worth considering. If not, sticking with what already works is totally fine.

Collapse
 
greenanttech profile image
Yohan Moore • Edited

This is the proper response. Even if it is the millionth JavaScript framework it is worth the attempt. At best it becomes something popular at worst you learned a lot about the problems that frameworks are trying to solve.

Thread Thread
 
m__mdy__m profile image
genix

I agree. every new project is an opportunity to learn and grow, even if it's as simple as a basic calculator. For me, even if Gland doesn't become a huge success, the process of building it, the challenges I’ve faced, and the research I've conducted are incredibly valuable. Every experience, including failures, contributes to future successes as long as we learn from them.

Collapse
 
killvung profile image
killvung

This one is unique due to its event driven nature

Collapse
 
ersansaribal profile image
Ersan Saribal

Short answer? Yes. Even thousands more, as long people are creating them in the name of innovation.

Collapse
 
sgebb profile image
sgebb

Not sure I understand this. You're raising an event users.read.server assuming that some channel is implementing it, but the raiser has no type info to know what the channel will return. Am I supposed to do just project wide searches for strings to figure things out?

Collapse
 
m__mdy__m profile image
genix

Good question! Right now, Gland’s event system is highly dynamic, allowing you to emit events without strict type constraints. While this provides flexibility, as you pointed out, it can make tracking responses harder.

One of the planned improvements is to introduce better type safety for emitted events, ensuring that the caller knows exactly what to expect. This could involve a type mapping system that links events to expected return types, making event-driven logic more predictable.

I’ll definitely take a closer look at this and explore ways to improve type inference in Gland’s event-driven flow. Thanks for the feedback!

Collapse
 
nurhusien1970 profile image
Omar N Idris

In technology I see a recycling of ideas. One technology claims an improvement then comes another with its own baggages. I like the dynamics but the big question is why has not humanity succeeded in bringing a better technology we can all agree on? Is it even possible? What is wrong with us?

Collapse
 
m__mdy__m profile image
genix

The problem is not with us, but with our idea of ​​progress. Perhaps progress does not mean reaching a final and perfect technology at all, but rather the same constant movement between ideas, tests and failures.

Collapse
 
mr_nova profile image
Ijeoma M. Jahsway

I like @m_mdy_m response but here's another perspective. Maybe the problem is just that humans can't remain fixed on one thing. They say change is nature and we die the moment we stop changing and improving.

If all humanity agrees to use one perfect framework then that eliminates variety and subsequently, change. There has to be options.

Collapse
 
tareksalem profile image
tareksalem

Nice work, liked the idea, I would suggest to go beyond controllers and give more concrete implementation to leverage ore advanced patterns and architectures like domain driven design and clean architecture, I think as framework if u give some guideline and specific patterns to leverage those architectures with the use of events it will be very awesome

Also we need to think how can we build a package that uses events for db interactions and managing transactions
One last thing, we can use this pattern to add extra functionality for saving all interactions happen on the server through events, for example controller emits to service and service emits to db then emits back controller then to channel, we can add one extra functionality that records those events so it can be very useful for logs and traces

Collapse
 
m__mdy__m profile image
genix

Thanks for the great insights! I really like the idea of going beyond just controllers and providing structured guidelines for architectures like DDD and Clean Architecture. That’s definitely something worth exploring—especially how event-driven patterns can naturally fit into these architectures and enhance modularity and maintainability.

I've already been working extensively on this idea and actively exploring ways to integrate these principles into Gland. Additionally, I plan to release third-party modules for database interactions that seamlessly integrate with Gland’s event-driven approach. These modules will provide a clean, decoupled way to manage data without tightly coupling the database logic to the application’s core.

In the future, I will also publish detailed documentation and guidelines on how to build third-party extensions for Gland, ensuring that developers can easily create plugins and integrations that align with its architecture. Of course, this will come after Gland reaches a stable release.

I also love the suggestion about event-based logging and tracing. Implementing a system that tracks and records all emitted events could be incredibly useful for debugging, monitoring, and even replaying events for auditing purposes. This is definitely something I’ll be looking into as Gland evolves.

Really appreciate the feedback! If you have any concrete ideas, specific use cases, or even suggestions on how you'd like to see these features implemented, feel free to share—I’d love to refine Gland’s event-driven approach even further.

Collapse
 
kenzhemir profile image
Miras Kenzhegaliyev

Hey!

This is very interesting approach 👍 with event driven framework, I feel like writing state-machines in the backend would fit nicely.

I have a thought about the ctx.emit. I feel like it should not return result of the channel's event handler (10). Emitter usually should not know or care who handled the event and listeners should act on their own behalf.
Typically, if original emitter needs data, some listener would emit a response event, which the original emitter would be listening to.

Collapse
 
m__mdy__m profile image
genix

Thank you for your thoughtful comment! You’ve raised a really important point about how event-driven systems should work, and I completely agree with the idea that emitters shouldn’t necessarily care about who handles the event or how it’s handled. This is actually one of the core principles behind Gland’s design, and I’d love to explain how we’ve approached this.

In Gland, ctx.emit is designed to give developers flexibility. While it can return the result of the handler, it doesn’t have to. This is intentional because we want to support both scenarios:

  1. When you need a response: Sometimes, you might want to ensure that an event has been handled before moving forward. For example, if you’re processing a payment, you might want to confirm that the payment was logged before proceeding. In this case, waiting for a response from the handler makes sense.

  2. When you don’t need a response: In other cases, like sending a notification, you might not care about the result at all. Here, you can simply emit the event and let the listeners handle it independently.

This flexibility allows developers to choose the right approach for their specific use case. If you prefer a more decoupled workflow where the emitter doesn’t wait for a response, you can design your system so that listeners emit follow-up events, and the emitter listens for those if needed. This keeps the emitter completely unaware of who’s handling the event or how it’s being handled, which aligns with your suggestion.

For example, imagine a scenario where a user signs up:

  • The controller emits a user:signup event.
  • A listener handles the event, saves the user to the database, and emits a user:created event.
  • Another listener picks up the user:created event and sends a welcome email.

In this case, the emitter doesn’t need to know about the database or the email service. It just broadcasts the event, and the listeners handle the rest.

That said, I completely understand your concern about the emitter potentially becoming too involved in the handling process. This is something I’ll keep in mind for future updates. For example, we could introduce stronger type constraints or event-response contracts to make it clearer when and how responses should be used. This would help developers avoid unintended coupling while still maintaining the flexibility that Gland offers.

Thanks again for sharing your thoughts! Your feedback is incredibly valuable, and it’s exactly this kind of discussion that helps make Gland better. If you have more ideas or suggestions, I’d love to hear them!

Collapse
 
mike_murphy_f7cd964d9a5af profile image
Mike Murphy

There are many cases where you may care that the event was handled before you moved on to something else, you may not care who has handled the event, but you many care that it has been handled.

Collapse
 
m__mdy__m profile image
genix

Thank you for highlighting that nuance! I completely agree—there are indeed many cases where you might need confirmation that an event was handled before moving on, even if you don't care who handled it. In Gland, we've intentionally designed ctx.emit to be flexible. You can simply emit an event without waiting for a response when that's all you need, or you can structure your workflow so that follow-up events or asynchronous callbacks provide the confirmation you require.

For instance, in scenarios where processing order is critical (like logging a payment or updating a user record), you can have a listener emit a follow-up event to signal that the handling is complete. This allows you to wait for confirmation without coupling your emitter to a specific handler. It’s all about letting you choose the approach that best fits your use case.

Collapse
 
shishantbiswas profile image
Shishant Biswas

Time to reset the clock

Collapse
 
zafar_khan_a9c906f42f5020 profile image
Zafar Khan

Gland is a new event-driven JavaScript web framework inspired by NestJS and Angular. It features dependency injection, controllers, and modular channels instead of providers, making apps more scalable and flexible. Using an Event-Driven System (EDS), actions are triggered by events rather than direct responses, enhancing modularity. The framework is lightweight, with minimal dependencies. Gland is still evolving, and feedback or contributions are welcome to refine its approach! 🚀

Collapse
 
zaunere profile image
hz

What we really need is a new web language.

Collapse
 
sanagkerkar profile image
Sanag Kerkar

Good, Keep going. I'm also making my own Framework for web still not have done that much. 😅

Collapse
 
xdefyinggravity profile image
Defying Gravity

Great idea! I see a lot of potential for microservices here. Breaking the code into asynchronous, event-driven modules can make it non-blocking and much more efficient, which really helps with the performance across services.

Collapse
 
webyguy profile image
Ted

Ive been commercially using Blazor for web development for over 3 years now, the freedom of no javascript and all the tooling is amazing, also really fast and responsive. Its 2025 no need for such an old language as Javascript any more and crazy tooling and dependancies.

Collapse
 
mavrik profile image
Artem M

Did you try moostjs? moost.org

Collapse
 
m__mdy__m profile image
genix

No! I did not try it. But now I looked at its documentation. I only saw controllers with packages named "event", but I didn't see any traces of eds in them. What is your eds implementation?

But I was attracted by your package @wooksjs/http-body. It's a good idea, maybe I'll try it.

Collapse
 
mavrik profile image
Artem M

I'm not quite sure what you mean behind "EDS" and what kind of "eds traces" you are looking for. But I can explain a few base concepts behind wooks/moost projects.
At the very core there is wooks project, which implements events lifecycle with support of "event context" and "router". So each hypothetical event can be defined via some route + handler. Within the handler an event context is available at any place via useAsyncEventContext composable (yes, wooks brings "composables" term from frontend like vue, which is pretty similar to react hooks, that's why it's called wooks (W-web, (h)ooks)).
Wooks supports adapters that bridges generic wooks events handling pipeline with the specific events sources/targets. For instance @wooksjs/event-http is an adapter for http events, so basically an alternative for express and fastify etc.
The next level of abstraction is covered by moost. That one brings nestjs-like decorators and wraps wooks and wooks adapters into moost+decorators. Basically you get pretty much nestjs experience but IMHO much less boilerplate and much easier event context handling and much easier custom decorators.
The package @wooksjs/http-body that you found interesting is just a set of composables for @wooksjs/event-http. Those composables are wrapped into decorators within @moostjs/event-http package.
If you want to take a glance of how it is supposed to work all together, just run npm create moost@latest, select http project and answer "no" to all the additional questions (or whatever you like, actually).

P.S. I think the workflow adapter for wooks/moost could be very close to what you're doing. And if you have a strong reason why events should be defined your way, I can create another adapter that would support Gland-like events. (you're welcome to contribute as well :) )

Thread Thread
 
m__mdy__m profile image
genix

Thanks for breaking down how Moost and Wooks work – really cool to see how you’re handling events with composables and adapters. Let me try to explain how Gland’s event-driven system (EDS) works differently, because I think it’s a fun twist on traditional frameworks.

In Gland, controllers don’t directly return responses. Instead, they just fire off events, and separate "channels" pick up those events to do the actual work. It’s like splitting the "what happened" from the "what to do about it." like this:

// When someone hits GET /users/123...
@Controller('users')
class UserController {
  @Get('/:id')
  getUser(ctx: Context) {
    try{
      // Emit an event – "Hey, someone wants user 123!"
      return ctx.emit('read:server', ctx);
    } catch(e:Error){
      ctx.emit('read:server:error',e)
    }
  }
}

// This channel listens for "read:server" events
@Channel('users')
class UserChannel {
  @On('read:server')
  get(ctx: Context) {
    // Go fetch the user from the DB
    return db.users.find(ctx.params.id);
  }

  @On('read:server:error')
  handleError(errot:Error) {
    // If something breaks, log it gracefully
    console.log('Oops:', error);
  }
}
Enter fullscreen mode Exit fullscreen mode

The beauty here is that the controller doesn’t know (or care) how the user is fetched – it just announces that someone asked for a user. The channel handles the nitty-gritty details. Want to add caching later? Just add another channel listener – no need to touch the controller.

Why this approach?

  • Decoupling: Channels and controllers don’t depend on each other.
  • Flexibility: Need to trigger 3 services when a user signs up? Just emit one event and let channels handle it.
  • Error handling: Errors become events you can handle centrally (like that read:server:error example).

And this isn’t limited to HTTP. The same pattern works for WebSockets, database changes, message queues, or anything else that emits events.

Your Moost/Wooks approach with composables looks super clean for HTTP workflows! Where I think Gland differs is treating all actions as events – even non-HTTP ones. For example, you could have channels that respond to database changes or external API calls using the same pattern.

  • Instead of just routing requests, Gland treats requests as events that get handled by channels.
  • Events are namespaced automatically (users:event, orders:created, etc.), so they stay organized.
  • It’s protocol-agnostic—you can use the same event-driven pattern across HTTP, WebSockets, or even microservices. (Of course, these things are currently implemented on my office, but it seems that it should work!)

Your adapter offer

That’s super generous! I’d love to explore how Gland’s event-first approach could work with Moost’s composable system. Maybe:

  • Using Moost’s context management in Gland’s channels
  • Letting Moost apps listen to Gland’s events (like bridging HTTP events to background tasks)

I’ll definitely play with npm create moost@latest this week – the HTTP body parsing decorators already gave me ideas for simplifying Gland’s validation pipes.

Thread Thread
 
mavrik profile image
Artem M

Ok, I think I am getting your idea. Although it raises many questions in my mind, like

1. Multiple listeners

What if multiple classes defined as @Channel('users') and subscribed for @On('read:server')?

2. What about providers?

In other words why not just using a provider concept like

@Controller('users')
class UserController {
  constructor(private users: UsersProvider) {}

  @Get('/:id')
  getUser(ctx: Context) {
    return this.users.getUser(ctx)
  }
}
Enter fullscreen mode Exit fullscreen mode

where UsersProvider is a provider for users that gets instantiated by DI and can be whatever you want.

3. Context

What is Context type? Is it the same for all kind of events? If no, how is user supposed to ensure proper types?

Speaking about wooks/moost

Your Moost/Wooks approach with composables looks super clean for HTTP workflows

Well, not only for HTTP workflows as http adapter is just one of the adapters for event handling pipeline. You can write websocket or kafka adapter to handle websocket/kafka events in similar matter. Currently there are cli and workflow adapters that streamline event processing with no relation to http.

In moost app I would solve the decoupling you're talking about via provider as described above.
When you want to add caching and/or logging, you just apply interceptors to event handler:

@Controller('users')
class UserController {
  constructor(private users: UsersProvider) {}

  @Get('/:id')
  @Intercept(loggerInterceptor)
  @Intercept(cachingInterceptor)
  // ... more interceptors
  getUser(@Param('id') id: string) {
    return this.users.getUser(id)
  }
}
Enter fullscreen mode Exit fullscreen mode

An interceptor has an initialization phase and 3 optional hooks:

  1. Interceptor is initialized before the handler arguments are resolved (before id has been parsed from the url). During that phase you're welcome to decide which hooks you're going to use.
  2. before hook is triggered after the handler arguments are resolved but before handler is triggered.
  3. after hook is triggered after the handler is processed (with no error)
  4. onError hook is triggered if a handler raised an error. On each step interceptor can overtake or overwrite the response.

Gland-like event processing

If you still want to just emit some event from the controller, it is also doable within moost framework.

  1. Define your wooks/moost adapter that would capture gland events
  2. Define decorators like @Channel and @On that would provide metadata for your adapter
  3. Use it with support of interceptors and event context out of the box.

Here's how moost code could look like if you had gland adapter:

@Controller('users')
class UserController {
  constructor(private gland: Gland) {}

  @Get('/:id')
  // interceptors supported
  getUser(@Param('id') id: string) {
    try{
      return this.gland.emit('read:server', {
        params: { id },
      });
    } catch(e:Error){
      return this.gland.emit('read:server:error', e)
    }
  }
}

@GlandChannel('users')
class UserChannel {
  @GlandHandler('read:server')
  // interceptors supported
  get(ctx: Context) {
    return db.users.find(ctx.params.id);
  }

  @GlandHandler('read:server:error')
  // interceptors supported
  handleError(errot:Error) {
    console.log('Oops:', error);
  }
}
Enter fullscreen mode Exit fullscreen mode

As I mentioned earlier the @moostjs/event-wf adapter is very close to what you are doing, it is a custom event type that can be raised out of controller. Although it is not about channels, it is about the exact wf implementation.

Collapse
 
naveed_rehman_a00013810ac profile image
Naveed Rehman

In any case, is it going to simplify seo related limitations with current frameworks? Nice project tho

Collapse
 
m__mdy__m profile image
genix

SEO is a separate concern when it comes to backend frameworks like Gland, NestJS, Express, Koa, Fastify, and others. These frameworks primarily focus on server-side logic, and SEO optimization typically relates more to frontend rendering and how search engines crawl and index the content.

The main goal of Gland is to provide an event-driven and modular system, offering innovative ways to implement logic in backend applications. While SEO is important, it falls outside the scope of what Gland is designed to address.

Collapse
 
kitanga_nday profile image
Kitanga Nday

For anyone who's used/using Kafka, would this make it easier to work with Kafka in some way?

Collapse
 
devking profile image
DEV King

"What? Again? This stupid country." -- M. Quimby.

Collapse
 
m__mdy__m profile image
genix

Haha, I get it. "What? Again?" -- M. Quimby. But hey, sometimes a new approach can bring fresh perspectives! Gland’s fully event-driven design isn't just another NestJS clone, it’s a different way of thinking about how data flows. Check it out: Gland GitHub.

Collapse
 
71104 profile image
Alberto La Rocca

This has to be a joke.

Collapse
 
m__mdy__m profile image
genix

why?

Collapse
 
enomatics profile image
enomatics • Edited

Is another framework really necessary?

Collapse
 
m__mdy__m profile image
genix
Collapse
 
gmcnicol profile image
Gareth McNicol • Edited

Do events have to be magic strings? Can they be typed somehow?

Cool ideas though!

Collapse
 
m__mdy__m profile image
genix

yes you can