Unread Notifications

Latest Notifications

No Notifications

You're all set! Start a discussion by leaving a comment on a lesson or replying to an existing comment.

Let's Build with AdonisJS

Rebuilding Jagr.Co, Password Reset & Account Locking

0 MIN READ
2 MONTHS AGO

In this livestream, we'll add a system to lock users' accounts after so many bad login attempts and we'll also add in the password reset flow.

Watch on YouTube

Join me live as I begin rebuilding Jagr.Co with AdonisJS. In this livestream, we'll add a system to lock users' accounts after so many bad login attempts. We'll add in the password reset flow. And, we'll start on adding social authentication. I have some off-screen work to do on the social auth to ensure my tokens and secrets are correct and will continue this next weekend!

― YouTube Video: https://www.youtube.com/watch?v=F1katmZxWgI
― Repository: https://github.com/jagr-co/jagr.co
― Newsletter: https://www.getrevue.co/profile/jagr_co/

Social Media:
― YouTube: https://www.youtube.com/jagr-co
― Twitter: https://twitter.com/jagr_co
― GitHub: https://github.com/jagr-co

Comment

  1. MrAvantiC
    Commented 7 months ago

    Hey there,

    this video brought up a question to me as someone learning the framework at the moment - I hope I'm not annoying you yet. :)

    While the Adonis-Documentation is very good and comprehensive, I could not find much material about how exactly services are supposed to be used. It seems like there is no "convention over configuration" when it comes to them.

    Now, when testing around in my small project, I found 2 possibilities:

    1. I could simply import the service where I needed it - for example in a controller - from "App/Services/MyService". Of course, this would require me to instantiate the service every time before using it, so not that nice.

    2. I followed this tutorial https://medium.com/@shemsiu/ioc-container-and-dependency-injection-in-adonis-v5-216774c2a476 -> as far as I understand the essential part with this is to instantiate the service in "providers/AppProvider.ts" and register it to be used application wide. Afterwards, I was able to import it in my controller from "@ioc:Namespace/MyService".

    Now, apparently there is another method "@inject" that you used in your video.

    However, what is the magic behind this? Does it automatically instantiante (and bind it to the controller?) your service when you reference it in the controller's constructor?

    What is the difference between this method and option 2) described above? When would you use the AppProvider?

    1. tomgobich
      Commented 7 months ago

      Hi MrAvantiC!

      First off, both of the above are valid approaches to services, however, there are different instances in which you’d want to use approach 2 over 1.

      Approach 1

      This approach is great for services that are going to be used specifically for controllers. For example, typically if you’d have a PostsController you’d use a PostService to compliment the controller. You can then either make that controller:

      • Filled with static methods, so you don’t need to instantiate anything

      • Filled with non-static methods, so you need to instantiate the service to use it’s methods

      • Have a mix-and-match of both static and non-static depending on the method

      As for @inject, you’re absolutely correct. So, without inject, to use a service with non-static methods it’d look something like this:

      import MyService from 'App/Services/MyService'
      
      export default class MyController {
        public myService: MyService
      
        constructor() {
          this.myService = new MyService()
        }
      
        public async example ({}) {
          return this.myService.someMethod()
        }
      }

      However, @inject will instantiate and bind the service for me to my controller, so instead I can do this:

      import MyService from 'App/Services/MyService'
      import { inject } from '@adonisjs/fold';
      
      @inject()
      export default class MyController {
        constructor(public myService: MyService) {}
      
        public async example ({}) {
          return this.myService.someMethod()
        }
      }

      This also works within services as well!

      import MyOtherService from 'App/Services/MyOtherService'
      import { inject } from '@adonisjs/fold';
      
      @inject()
      export default class MyService {
        constructor(public myOtherService: MyOtherService) {}
      }

      Approach 2

      With approach 1, the service is instantiated for the controller for each request. Meaning, you know the service instance is specific to your user’s individual request. With approach 2, since they’re using singleton the service is instantiated once when the server is booted. So, a service instance is shared by all users and all their requests. This makes approach 2 great if you need to make a service that maintains connections, maintains a third-party package instance, performs actions shared by multiple users, or if it’s just your preference.

      Which approach you use is a personal/company preference. I personally feel more comfortable knowing the service instance is specific to a single user’s individual request. It eliminates the worry of having cross-user side effects. So, I only use approach 2 when I specifically need to have a long-lived service.

      1. MrAvantiC
        Commented 7 months ago

        Hey Tom, thanks for your response!

        So, when you have methods that do not depend on the user/request/context, we could simply define these methods as static , import the service and use it without getting an instance of the class first, got it!

        However, in case I do need an instance of the service because context is required (like your BaseHttpService for example), what is the difference between using the AppProvider :

          public register() {
            // Register your own bindings
            this.app.container.bind('MyNamespace/MyService', () => new MyService())
          }

        …and using inject() ?

        Is it personal preference only again?
        I get that when you need a singleton the AppProvider approach is more useful but what about using bind ? This would again create one instance per request, right?

        1. tomgobich
          Commented 7 months ago

          My understanding is that using this.app.container.bind is similar to doing:

          class MyService {
          }
          
          export default new MyService()

          In that, if you import the service at the top-level of your controller it’ll only be instantiated once for that controller on the first request that controller handles, then that same instances will be continuously used for all requests that controller handles.

          You can test this by adding the following to your AppProvider’s register method.

          public register () {
              // Register your own bindings
              class TestService {
                public count: number = 0
                
                constructor() {
                  console.log('TestService > constructor')
                }
          
                public testing() {
                  this.count += 1
                  console.log('testing', this.count)
                }
              }
              this.app.container.bind('Test/TestService', () => new TestService())
          }

          Then, import it within two controllers and call the testing method in one method on each controller.

          import TestService from '@ioc:Test/TestService'
          
          export default class ExampleController {
            public async index({ view }) {
              return view.render('example')
            }
          }

          Then request the pages and refresh each page. The console output will be something like this, depending on the order you refresh.

          TestService > constructor
          testing 1
          TestService > constructor
          testing 1
          testing 2
          testing 2

          So, it’s getting instantiated once per controller and keeping that instance for subsequent requests for that controller.

          1. MrAvantiC
            Commented 7 months ago

            I see, so basically we have:

            1. AppProvider + singleton() => One shared instance for all imports

            2. AppProvider + bind() => One instance per import (e.g. controller) which will be re-used for subsequent requests

            3. inject() => One instance per request which is great if you rely on request-specific data (e.g. HttpContext)

            Appreciate your help in understanding all of this! :)

            1. tomgobich
              Commented 7 months ago

              Exactly!! Anytime, happy to have been able to help! :)

Prepared By

Tom Gobich

Burlington, KY

Owner of Adocasts, JavaScript developer, educator, PlayStation gamer, burrito eater.

Visit Website