{"id":20688564,"url":"https://github.com/tripolskypetr/node-scoped-service","last_synced_at":"2026-05-07T13:13:24.585Z","repository":{"id":263112478,"uuid":"888889746","full_name":"tripolskypetr/node-scoped-service","owner":"tripolskypetr","description":"Scoped services similar to ASP.Net Core ported to NodeJS. Framework agnostic","archived":false,"fork":false,"pushed_at":"2024-11-22T07:28:58.000Z","size":95,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-17T16:56:29.406Z","etag":null,"topics":["api","appwrite","asp-net-core","backend","dependency-injection","microservice","nodejs","scoped","scoped-service","server-side-rendering"],"latest_commit_sha":null,"homepage":"https://github.com/react-declarative/react-declarative","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tripolskypetr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-15T07:48:09.000Z","updated_at":"2024-11-22T07:29:02.000Z","dependencies_parsed_at":"2024-11-16T09:26:04.121Z","dependency_job_id":"a312be96-6d71-4dbf-be66-71f6a2b3c84d","html_url":"https://github.com/tripolskypetr/node-scoped-service","commit_stats":null,"previous_names":["tripolskypetr/node-scoped-service"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tripolskypetr%2Fnode-scoped-service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tripolskypetr%2Fnode-scoped-service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tripolskypetr%2Fnode-scoped-service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tripolskypetr%2Fnode-scoped-service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tripolskypetr","download_url":"https://codeload.github.com/tripolskypetr/node-scoped-service/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242951172,"owners_count":20211572,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","appwrite","asp-net-core","backend","dependency-injection","microservice","nodejs","scoped","scoped-service","server-side-rendering"],"created_at":"2024-11-16T23:06:07.157Z","updated_at":"2026-05-07T13:13:19.557Z","avatar_url":"https://github.com/tripolskypetr.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# node-scoped-service\n\n\u003e Using Appwrite on a backend side by using JWT Authentication\n\n![screenshot](./docs/screenshot.PNG)\n\n```tsx\nimport { scoped } from 'di-scoped';\n\nconst TestClass = scoped(class {\n\n    constructor(private name: string) {\n    }\n\n    test() {\n        console.log(`Hello, ${this.name}`);\n    }\n});\n\n\nTestClass.runInContext(() =\u003e {\n\n    new TestClass().test(); // Hello, Peter\n\n}, \"Peter\")\n```\n\n\n## ASP.Net Core Scoped Services for NodeJS\n\nWhen working with appwrite you can passthrough the client session from a frontend to the backend side by using [JWT Login](https://appwrite.io/docs/products/auth/jwt)\n\n**Frontend code**\n\n```tsx\nimport * as Appwrite from 'appwrite';\n\nconst client = new Appwrite.Client();\nclient\n    .setEndpoint(\"https://cloud.appwrite.io/v1\")\n    .setProject(\"672f8382002190141578\");\n\nconst account = new Appwrite.Account(client);\n\nconst session = await account.createEmailPasswordSession(\n    email,\n    password\n);\n\nconst { jwt } = await account.createJWT();\n```\n\n**Backend code**\n\n```tsx\nimport {\n  Client,\n  Databases,\n  Storage,\n} from \"node-appwrite\";\n\nconst client = new Client();\nclient\n    .setEndpoint(CC_APPWRITE_ENDPOINT_URL)\n    .setProject(CC_APPWRITE_PROJECT_ID)\n    .setJWT(jwt)\n    .setLocale(\"en-GB\");\nconst databases = new Databases(client);\nconst storage = new Storage(client);\n```\n\nThe problem is if you are working with Express there is no way to skip the `Client` object manual creation on each request. After creation, you will have to keep the reference in each function arguments so the code will be dirty.\n\n**Express code**\n\n```tsx\napp.post('/todos', async (req, res) =\u003e {\n    const jwt = req.headers.authorization.split(' ')[1];\n\n    const client = new Client();\n    client\n        .setEndpoint('https://[APPWRITE_ENDPOINT]/v1')\n        .setProject('[APPWRITE_PROJECT_ID]')\n        .setJWT(jwt)\n\n    const database = new Database(client);\n    const account = new Account(client);\n\n    const { title } = req.body;\n\n    const newTodo = await database.createDocument(collectionId, 'unique()', {\n      title\n    });\n\n    res.status(200).json(newTodo);\n});\n```\n\nUsually, a common backend application has many CRUD routes. Therefore, automating client creation is necessary; otherwise, our code will become disorganized...\n\n## The solution\n\nThe dependency injection pattern will organize the data flow\n\n```tsx\n\nexport class TodoDbService {\n\n    private readonly appwriteService = inject\u003cTAppwriteService\u003e(TYPES.appwriteService);\n\n    findAll = async () =\u003e {\n        return await resolveDocuments\u003cITodoRow\u003e(listDocuments(CC_APPWRITE_TODO_COLLECTION_ID));\n    };\n\n    findById = async (id: string) =\u003e {\n        return await this.appwriteService.databases.getDocument\u003cITodoDocument\u003e(\n            CC_APPWRITE_DATABASE_ID,\n            CC_APPWRITE_TODO_COLLECTION_ID,\n            id,\n        );\n    };\n\n    create = async (dto: ITodoDto) =\u003e {\n        return await this.appwriteService.databases.createDocument\u003cITodoDocument\u003e(\n            CC_APPWRITE_DATABASE_ID,\n            CC_APPWRITE_TODO_COLLECTION_ID,\n            this.appwriteService.createId(),\n            dto,\n        );\n    };\n\n    update = async (id: string, dto: Partial\u003cITodoDto\u003e) =\u003e {\n        return await this.appwriteService.databases.updateDocument\u003cITodoDocument\u003e(\n            CC_APPWRITE_DATABASE_ID,\n            CC_APPWRITE_TODO_COLLECTION_ID,\n            id,\n            dto,\n        );\n    };\n\n    remove = async (id: string) =\u003e {\n        return await this.appwriteService.databases.deleteDocument(\n            CC_APPWRITE_DATABASE_ID,\n            CC_APPWRITE_TODO_COLLECTION_ID,\n            id,\n        );\n    };\n\n};\n```\n\nThe JWT token for Authentication should be passed to AppwriteService by using constructor arguments\n\n```tsx\nexport const AppwriteService = scoped(class {\n//                            ^^^^^^^^\n\n  public client: Client = null as never;\n  public storage: Storage = null as never;\n  public databases: Databases = null as never;\n\n  public createId = () =\u003e {\n    return ID.unique();\n  };\n\n  public upsertDocument = async (\n    COLLECTION_ID: string,\n    id: string,\n    body: object\n  ) =\u003e {\n    try {\n      return readTransform(\n        await this.databases.createDocument(\n          CC_APPWRITE_DATABASE_ID,\n          COLLECTION_ID,\n          id,\n          writeTransform(body)\n        )\n      );\n    } catch (error) {\n      if (error instanceof AppwriteException) {\n        return readTransform(\n          await this.databases.updateDocument(\n            CC_APPWRITE_DATABASE_ID,\n            COLLECTION_ID,\n            id,\n            writeTransform(body)\n          )\n        );\n      }\n      throw error;\n    }\n  };\n\n  constructor(public jwt: string) {\n//            ^^^^^^^^^^^^^^^^^^\n    console.log(\"AppwriteService CTOR\", jwt)\n    const client = new Client();\n    client\n      .setEndpoint(CC_APPWRITE_ENDPOINT_URL)\n      .setProject(CC_APPWRITE_PROJECT_ID)\n      .setJWT(jwt)\n      .setLocale(\"en-GB\");\n    const databases = new Databases(client);\n    const storage = new Storage(client);\n    {\n      this.client = client;\n      this.databases = databases;\n      this.storage = storage;\n    }\n  }\n\n});\n\nexport type TAppwriteService = InstanceType\u003ctypeof AppwriteService\u003e;\n```\n\nThe JWT token should be taken from HTTP Context when the request recieved on a express side. Here comes the scoped service feature: by calling `AppwriteService.runInContext(async () =\u003e {` you will reinstantiate the AppwriteService in the current `async_hooks` execution context with the new `constructor` argument which contains actual JWT token\n\n```javascript\nimport { ioc, AppwriteService } from './lib';\n\n...\n\nrouter.get(\"/api/v1/count_todo\", (req, res) =\u003e {\n  const jwtToken = getAuthToken(req);\n\n  AppwriteService.runInContext(async () =\u003e {\n\n    micro.send(\n      res, \n      200, \n      await ioc.todoRequestService.getTodoCount()\n    );\n\n  }, jwtToken);\n\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftripolskypetr%2Fnode-scoped-service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftripolskypetr%2Fnode-scoped-service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftripolskypetr%2Fnode-scoped-service/lists"}