{"id":16241726,"url":"https://github.com/getlarge/ticketing","last_synced_at":"2025-03-16T12:32:51.912Z","repository":{"id":46747223,"uuid":"423703830","full_name":"getlarge/ticketing","owner":"getlarge","description":"Microservices example using NestJS, Kubernetes in Nx workspace","archived":false,"fork":false,"pushed_at":"2024-04-09T19:06:05.000Z","size":10712,"stargazers_count":52,"open_issues_count":10,"forks_count":8,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-04-09T21:18:53.751Z","etag":null,"topics":["angular","docker","esmodules","fastify","kubernetes","microservices","nestjs","nodejs","nx-workspace","ory","rabbitmq"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/getlarge.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"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}},"created_at":"2021-11-02T04:13:45.000Z","updated_at":"2024-04-15T12:38:15.080Z","dependencies_parsed_at":"2024-01-07T10:26:26.998Z","dependency_job_id":"e0ef08fa-98c6-4ba1-9b77-95b82df59528","html_url":"https://github.com/getlarge/ticketing","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Fticketing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Fticketing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Fticketing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Fticketing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/getlarge","download_url":"https://codeload.github.com/getlarge/ticketing/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243814876,"owners_count":20352054,"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":["angular","docker","esmodules","fastify","kubernetes","microservices","nestjs","nodejs","nx-workspace","ory","rabbitmq"],"created_at":"2024-10-10T14:08:27.465Z","updated_at":"2025-03-16T12:32:51.275Z","avatar_url":"https://github.com/getlarge.png","language":"TypeScript","readme":"# Ticketing (think concert tickets)\n\n📚 This repository is a learning resource for building a full-stack application with [Nx](https://nx.dev), [NestJS](https://nestjs.com), [Angular](https://angular.dev), and [Ory](https://ory.sh).\n\nIt shows:\n\n- how to organize internal dependencies\n- tricks to use [Fastify](https://fastify.dev) with `NestJS`\n- tricks to consume and produce ES6 modules with `NestJS`, `Jest` and `Nx`\n- how to integrate `Ory` in `NestJS` and `Angular` apps for authentication and authorization flows\n- how to set up `Ory` in local and remote working environments\n- how to use [RabbitMQ](https://www.rabbitmq.com) with `NestJS`\n- how to define/validate environment variables\n- how to containerize Nx apps with [Docker](https://www.docker.com)\n- how to integrate Nx into a [Kubernetes](https://kubernetes.io) workflow\n- how to dynamically rebuild `Docker` images based on the `Nx` project graph\n\nAnd there is even a list of [challenges](./CHALLENGES.md)! 🏁\n\n\u003e [!NOTE]\n\u003e This project is inspired by Stephen Grider's [Microservices with Node JS and React](https://www.udemy.com/course/microservices-with-node-js-and-react/) course on Udemy, starting in chapter 5.\n\u003e You can find the source code for the course [here](https://github.com/StephenGrider/ticketing).\n\n## User story\n\n```mermaid\njourney\n  title Exchanging tickets online\n  section Register\n    Create a user: 3: Seller, Buyer\n    Signin : 3: Seller\n  section Search tickets\n    Find by name: 5: Buyer, Seller\n  section Create ticket offer\n    Write description: 3: Seller\n    Define price: 3: Seller\n  section Buy ticket\n    Reserve tickets: 5: Buyer\n    Place order: 3: Buyer\n    Pay ticket: 3: Buyer\n  section Check orders\n    See orders list: 5: Seller, Buyer\n```\n\n## Architecture\n\n```mermaid\n---\ntitle: Ticketing architecture\n---\nflowchart LR\n%% defining styles\n    classDef app fill:#f7e081,stroke:#333,stroke-width:1px\n\n%% defining entities\n    FE[Angular app]\n    LB[Nginx proxy]\n    A[Auth API]\n    A-M[(Mongo)]\n    T[Tickets API]\n    T-M[(Mongo)]\n    O[Orders API]\n    O-M[(Mongo)]\n    P[Payments API]\n    P-M[(Mongo)]\n    St[Stripe]\n    E[Expiration API]\n    E-R[(Redis)]\n    RMQ[RabbitMQ]\n    Kr[Kratos]\n    Ke[Keto]\n    Hy[Hydra]\n\n%% assigning styles to entities\n    %%AS,OS,ES,TS,PS:::service\n    %%class A,T,O,E,P,FE app;\n\n%% flow\n    FE --\u003e|HTTP| LB\n    FE --\u003e|HTTP| St \u003c--\u003e|HTTP| PS\n    FE --\u003e|HTTP| ORY \u003c--\u003e|HTTP| AS\n    LB ---\u003e|HTTP| AS \u0026 TS \u0026 OS \u0026 PS\n    RMQ \u003c-.-\u003e|AMQP| TS \u0026 OS \u0026 ES \u0026 PS\n    TS \u0026 OS \u0026 PS --\u003e|HTTP| ORY\n    subgraph AS [Auth service]\n    direction LR\n    A --\u003e A-M\n    end\n    subgraph ORY [Ory Network]\n    direction LR\n    Kr\n    Ke\n    Hy\n    end\n    subgraph TS [Tickets service]\n    direction LR\n    T --\u003e T-M\n    end\n    subgraph OS [Orders service]\n    direction LR\n    O --\u003e O-M\n    end\n    subgraph ES [Expiration service]\n    direction LR\n    E \u003c--\u003e E-R\n    end\n    subgraph PS [Payments service]\n    direction LR\n    P --\u003e P-M\n    end\n\n```\n\n## Entities\n\n```mermaid\n---\ntitle: Ticketing entities\n---\nerDiagram\n    User ||--o{ Ticket : owns\n    User ||--o{ Order : owns\n    User ||--o{ Payment : owns\n    Ticket ||--o| Order : \"bound to\"\n    Order ||--o| Payment : \"bound to\"\n\n    User {\n        int id PK\n        string email \"unique\"\n    }\n    Ticket {\n        string id PK\n        string title\n        float price\n        int version\n        string userId FK\n        string orderId FK \"Optional\"\n    }\n    Order {\n        string id PK\n        string status\n        int version\n        string ticketId FK\n        string userId FK\n    }\n    Payment {\n        string id PK\n        string orderId FK\n        string stripeId \"Charge ID from Stripe\"\n        int version\n    }\n```\n\n## Permissions\n\nPermissions are granted or denied using Ory Permissions (Keto) [policies](https://www.ory.sh/docs/keto/).\n\n```mermaid\n---\ntitle: Entities namespaces and relationships\n---\nclassDiagram\n\n    NamespaceRelations *-- Namespace\n    NamespacePermissions *-- Namespace\n    Namespace \u003c|-- User\n    Namespace \u003c|-- Group\n    Namespace \u003c|-- Ticket\n    Namespace \u003c|-- Order\n    Namespace \u003c|-- Payment\n    Group o-- User : \"members\"\n    Ticket o-- User : \"owners\"\n    Order o-- User : \"owners\"\n    Order *-- Ticket : \"parents\"\n    Payment o-- User : \"owners\"\n    Payment *-- Order : \"parents\"\n\n    class Context {\n      \u003c\u003cInterface\u003e\u003e\n      subject: never;\n    }\n\n    class NamespaceRelations {\n        \u003c\u003cInterface\u003e\u003e\n        +[relation: string]: INamespace[]\n    }\n\n    class NamespacePermissions {\n        \u003c\u003cInterface\u003e\u003e\n        +[method: string]: (ctx: Context) =\u003e boolean\n    }\n\n    class Namespace {\n        \u003c\u003cInterface\u003e\u003e\n        -related?: NamespaceRelations\n        -permits?: NamespacePermissions\n    }\n\n    class User {\n    }\n\n    note for Group \"\u003ci\u003eUsers\u003c/i\u003e can be \u003cb\u003emembers\u003c/b\u003e of a \u003ci\u003eGroup\u003c/i\u003e\"\n    class Group {\n        +related.members: User[]\n    }\n\n    note for Ticket \"\u003ci\u003eUsers\u003c/i\u003e (in owners) are allowed to \u003cb\u003eedit\u003c/b\u003e. \\nHowever \u003ci\u003eTicket\u003c/i\u003e \u003cb\u003eowners\u003c/b\u003e cannot \u003cb\u003eorder\u003c/b\u003e a Ticket. \\nImplicitly, anyone can \u003cb\u003eview\u003c/b\u003e Tickets\"\n    class Ticket {\n        +related.owners: User[]\n        +permits.edit(ctx: Context): boolean\n        +permits.order(ctx: Context): boolean\n    }\n\n    note for Order \"\u003ci\u003eOrder\u003c/i\u003e is bound to a \u003ci\u003eTicket\u003c/i\u003e. \\n\u003ci\u003eUsers\u003c/i\u003e (in \u003cb\u003eowners\u003c/b\u003e) are allowed to \u003cb\u003eview and edit\u003c/b\u003e. \\n \u003ci\u003eOrder's Ticket\u003c/i\u003e \u003cb\u003eowners\u003c/b\u003e are allowed to \u003cb\u003eview\u003c/b\u003e.\"\n    class Order {\n        +related.owners: User[]\n        +related.parents: Tickets[]\n        +permits.edit(ctx: Context): boolean\n        +permits.view(ctx: Context): boolean\n    }\n\n    note for Payment \"\u003ci\u003ePayment\u003c/i\u003e is bound to a Ticket's \u003ci\u003eOrder\u003c/i\u003e. \\n\u003ci\u003eUsers\u003c/i\u003e (in \u003cb\u003eowners\u003c/b\u003e) are allowed to \u003cb\u003eview and edit\u003c/b\u003e. \\n\u003ci\u003ePayment\u003c/i\u003e can be \u003cb\u003eviewed\u003c/b\u003e by \u003ci\u003eOrder's Ticket\u003c/i\u003e \u003cb\u003eowners\u003c/b\u003e.\"\n    class Payment {\n        +related.owners: User[]\n        +related.parents: Order[]\n        +permits.edit(ctx: Context): boolean\n        +permits.view(ctx: Context): boolean\n    }\n```\n\n## Events\n\n```mermaid\nsequenceDiagram\n  participant Tickets service\n  participant Orders service\n  participant Payments service\n  participant Expiration service\n  participant RMQ\n\n  loop ticket:created\n    %% event emitted by tickets service\n    Tickets service-\u003e\u003e+RMQ: Publish new ticket\n    RMQ--\u003e\u003e-Orders service: Dispatch new ticket\n    Note left of Orders service: Orders service needs to know \u003cbr\u003e about tickets that can be reserved.\n  end\n\n  loop ticket:updated\n    %% event emitted by tickets service\n    Tickets service-\u003e\u003e+RMQ: Publish updated ticket\n    RMQ--\u003e\u003e-Orders service: Dispatch updated ticket\n    Note left of Orders service: Orders service needs to know \u003cbr\u003e if tickets price have changed and \u003cbr\u003eif they are successfully reserved\n  end\n\n  loop order:created\n    %% event emitted by orders service\n    Orders service-\u003e\u003e+RMQ: Publish new order\n    par RMQ to Tickets service\n      RMQ-\u003e\u003eTickets service: Dispatch new order\n      Note left of Tickets service: Tickets service needs to know\u003cbr\u003eif a ticket has been reserved\u003cbr\u003eto prevent its edition.\n      and RMQ to Payments service\n      RMQ-\u003e\u003ePayments service: Dispatch new order\n      Note left of Payments service: Payments service needs to know\u003cbr\u003ethere is a new order that a user\u003cbr\u003emight submit a payment for.\n      and RMQ to Expiration service\n      RMQ-\u003e\u003eExpiration service: Dispatch new order\n      Note left of Expiration service: Expiration service needs to start\u003cbr\u003ea timer to eventually time out\u003cbr\u003ethis order.\n    end\n  end\n\n\n  loop order:cancelled\n    %% event emitted by orders service\n    Orders service-\u003e\u003e+RMQ: Publish cancelled order\n    par RMQ to Tickets service\n      RMQ-\u003e\u003eTickets service: Dispatch cancelled order\n      Note left of Tickets service: Tickets service should unreserve ticket\u003cbr\u003eif the corresponding order has been\u003cbr\u003ecancelled so this ticket can be \u003cbr\u003eedited again\n      and RMQ to Payments service\n      RMQ-\u003e\u003ePayments service: Dispatch cancelled order\n      Note left of Payments service: Payments service should know that\u003cbr\u003eany incoming payments for this order\u003cbr\u003eshould be rejected\n    end\n  end\n\n  loop expiration:complete\n    %% event emitted by expiration service\n    Expiration service-\u003e\u003e+RMQ: Publish complete expiration\n    par RMQ to Orders service\n      RMQ-\u003e\u003eOrders service: Dispatch expired order\n      Note left of Orders service: Orders service needs to know that an order\u003cbr\u003ehas gone over the 15 minutes time limit.\u003cbr\u003eIt is up to the order service to decide\u003cbr\u003e wether or not to cancel the order.\n    end\n  end\n\n  loop payment:created\n    %% event emitted by payments service\n    Payments service-\u003e\u003e+RMQ: Publish payment created\n    par RMQ to Orders service\n      RMQ-\u003e\u003eOrders service: Dispatch payment created\n      Note left of Orders service: Orders service needs to know that an order\u003cbr\u003ehas been paid for.\n    end\n  end\n```\n\n## Environment variables\n\nI am using [dotenv-vault](https://vault.dotenv.org/) to manage environment variables.\nYou can fork the project and use the following links to create your Dotenv project by forking the corresponding Dotenv project.\n\n| project                                    | fork                                                                                                                                                                              |\n| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| [docker](./.env.vault)                     | [![fork with dotenv-vault](https://badge.dotenv.org/fork.svg?r=1)](https://vault.dotenv.org/project/prj_7dc625e23d6655d88f5fc340b5118f4f645cfe8b150522df15202cfe7390f9ad/example) |\n| [auth](./apps/auth/.env.vault)             | [![fork with dotenv-vault](https://badge.dotenv.org/fork.svg?r=1)](https://vault.dotenv.org/project/prj_04b3110a437ffb0447545472cbc5a24cb10a00175f184291e0f87e3d3713b156/example) |\n| [expiration](./apps/expiration/.env.vault) | [![fork with dotenv-vault](https://badge.dotenv.org/fork.svg?r=1)](https://vault.dotenv.org/project/prj_d12d931099f7e9b4188b46de1b2a6f3e05d6eb84df4cf153bd99fc56b724bf17/example) |\n| [moderation](./apps/moderation/.env.vault) | [![fork with dotenv-vault](https://badge.dotenv.org/fork.svg?r=1)](https://vault.dotenv.org/project/vlt_a873660ea5f4c049dab8f0c5900682319d22dbafdb16523a8fece8fa32c30f58/example) |\n| [orders](./apps/orders/.env.vault)         | [![fork with dotenv-vault](https://badge.dotenv.org/fork.svg?r=1)](https://vault.dotenv.org/project/prj_d9f6776d63a2f5a2f5344e560b46d73682de9609903b0008854038df27a57663/example) |\n| [payments](./apps/payments/.env.vault)     | [![fork with dotenv-vault](https://badge.dotenv.org/fork.svg?r=1)](https://vault.dotenv.org/project/prj_f06df46b19ae0475561401ec975eae4971efba53f4882716572b79016fd4127d/example) |\n| [tickets](./apps/tickets/.env.vault)       | [![fork with dotenv-vault](https://badge.dotenv.org/fork.svg?r=1)](https://vault.dotenv.org/project/prj_2ab6048cf86dd07e93a4eb095220e88b7428731454962e0f7ad9eb51fde7e2cc/example) |\n\n## Useful commands\n\n... to run after configuring the required environment variables\n\n```bash\n# build custom Nginx Proxy\nyarn docker:proxy:build\n\n# build custom RabbitMQ node\nyarn docker:rmq:build\n\n# start the Storage and Broker dependencies (mongo, redis, rabbitmq)\nyarn docker:deps:up\n\n# start Nginx Proxy (for backend services and frontend app)\nyarn docker:proxy:up\n\n# Generate Ory network configuration from .env\nyarn ory:generate:kratos\nyarn ory:generate:keto\n\n# start Ory network (Kratos and Keto with database migrations)\nyarn docker:ory:up\n\n# start backend services\nyarn start:backend\n\n# start (Angular) frontend app\nyarn start:frontend:local\n\n```\n","funding_links":[],"categories":["Examples"],"sub_categories":["Ory Ecosystem"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetlarge%2Fticketing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgetlarge%2Fticketing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetlarge%2Fticketing/lists"}