{"id":19572508,"url":"https://github.com/maderarasto/ts-nodejs-mvc","last_synced_at":"2026-05-13T08:03:33.992Z","repository":{"id":179934955,"uuid":"664345852","full_name":"maderarasto/ts-nodejs-mvc","owner":"maderarasto","description":"Template for simple MVC Node.js application using TypeScript language.","archived":false,"fork":false,"pushed_at":"2023-08-08T21:25:49.000Z","size":187,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-09T03:41:45.630Z","etag":null,"topics":["liquidjs","mvc","mysql","typescript"],"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/maderarasto.png","metadata":{"files":{"readme":"readme.md","changelog":null,"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,"publiccode":null,"codemeta":null}},"created_at":"2023-07-09T17:33:59.000Z","updated_at":"2023-08-08T21:38:25.000Z","dependencies_parsed_at":null,"dependency_job_id":"70c2a4ac-b724-4bc5-80f6-10b88053f6fd","html_url":"https://github.com/maderarasto/ts-nodejs-mvc","commit_stats":null,"previous_names":["maderarasto/ts-nodejs-mvc"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maderarasto%2Fts-nodejs-mvc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maderarasto%2Fts-nodejs-mvc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maderarasto%2Fts-nodejs-mvc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maderarasto%2Fts-nodejs-mvc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maderarasto","download_url":"https://codeload.github.com/maderarasto/ts-nodejs-mvc/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240840102,"owners_count":19866167,"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":["liquidjs","mvc","mysql","typescript"],"created_at":"2024-11-11T06:27:19.024Z","updated_at":"2026-05-13T08:03:33.837Z","avatar_url":"https://github.com/maderarasto.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TypeScript Node.js MVC\nThe project is template for simple [Node.js](https://nodejs.org/en) application using [TypeScript](https://www.typescriptlang.org/) language. The application si based on MVC architecture within which data management is covered by [MySQL](https://www.mysql.com/) database and rendering views using [LiquidJS](https://liquidjs.com/) library.\n\nWith [LiquidJS](https://liquidjs.com/) library you can define custom views with additional helper functions that help you write view templates more easily and access to data from controller.The more information about LiquidJS you can find on its authors webiste [https://liquidjs.com](https://liquidjs.com).\n\n**Tags**: [TypeScript](https://www.typescriptlang.org/), [Node.js](https://nodejs.org/en), [MySQL](https://www.mysql.com/), [LiquidJS](https://liquidjs.com/), Dependency Injection\n## Table of contents\n1. [Motivation](#motivation)\n2. [State of project](#state-of-project)\n3. [Configuration](#configuration)\n4. [Getting started](#gettings-started)\n    - [Database](#database)\n    - [Models](#models)\n    - [Controllers](#controllers)\n    - [Services](#services)\n    - [Dependency Injection Container](#dependency-injection-container)\n    - [Views](#views)\n    - [Error handling](#error-handling)\n\n## Motivation\nThis project was created for purpose to easily create server application with API for future projects and to apply [Typescript](https://www.typescriptlang.org/) and [Node.js](https://nodejs.org/en) skills.\n\n## State of project\n#### Done\n✅ Querying database with MySQL connector\u003cbr /\u003e\n✅ Models with basic CRUD operations and get/set accessors based on properties\u003cbr /\u003e\n✅ Using controllers with registered routes in config\u003cbr /\u003e\n✅ Error handling to send error JSON (currenly not for views)\u003cbr /\u003e\n✅ Rendering view templates using LiquidJS\u003cbr /\u003e\n✅ Authenticating user\u003cbr /\u003e\n✅ Session storage through file or databas driver\u003cbr /\u003e\n\n#### In development\n⌛ Authenticating via JWT tokens\u003cbr /\u003e\n⌛ Validating input from requests\u003cbr /\u003e\n⌛ Defining JSON responses based on model\u003cbr /\u003e\n⌛ Defining relations between models\u003cbr /\u003e\n\n## Configuration\nThis is [Node.js](https://nodejs.org/en) project, so before installing [download and install latest Node.js](https://nodejs.org/en/download/current).\nAfter installation you can install project dependencie  with command:\n```bash\nnpm install\n```\nAfter installing dependencies you can rename file `.env.example` to `.env` and set up name and port of your application:\n```\nAPP_NAME=NodeJS MVC\nAPP_PORT=3000\n...\n```\n\nAfter setup you can run server application with command:\n```bash\nnpx ts-node src/app.ts\n```\nor with pre-defined script `start`\n```bash\nnpm run start\n```\n## Gettings Started\n### Database\nFirst you will need define environment variables in file `.env` to ensure that connection to [MySQL](https://www.mysql.com/) database can be done. Fill out `DB_HOST`, `DB_USER`, `DB_PASS` and `DB_NAME` variables:\n```\n# DATABASE SETTINGS\nDB_HOST=\nDB_USER=\nDB_PASS=\nDB_NAME=\n```\nThen you can execute SQL queries using async method `execute` on object `DB`. Don't forget use `await` or to process a `Promise` to access result of execution.\n```typescript\nimport DB, {RowData} from 'database/DB';\n\n...\n\nconst result = await DB.execute('SELECT * FROM users') as RowData[];\n...\n```\nWhen you are using parameters that you pass to SQL query, you should replace values in SQL query with symbol `?` and values then pass to second parameter, in which expects array of values as they follow each other in SQL query.\n```typescript\nimport DB, {ResultSetHeader} from 'database/DB';\n\n...\nconst result = await DB.execute(`\n    INSERT INTO users (first_name, last_name, login, password)\n    VALUES (?, ?, ?, ?)\n`, [firstName, lastName, login, password]) as ResultSetHeader;\n...\n```\nWith DB you can also begin, commit and rollback database transactions. You can use it like this:\n```typescript\nimport DB from 'database/DB';\n\n...\ntry {\n    await DB.beginTransaction();\n\n    ...\n    await DB.commit();\n} catch {\n    await DB.rollback();\n}\n\n```\n### Models\nModels are objects that interact with database and each model corresponds to each database table. Models allow you easily interact with database using their methods such as finding models by their ids, saving them with current state of their data or deleting them.\n\n#### Creating model\nFirst you will need to craete new class for your model. The model name should by capitalized and in form of singular of model as example `class User`. Your model should also contains properties that corresponds with your table columns and their names have to be the same as your table columns. \n\nEach property property should has `public` access and be marked as possible undefined with symbol `?`. Also above each property should use decorator `useField()` to ensure that all properties have get/set accessors. With decorator `useField()` you can also alter setter so in example password can be hashable.\n\n```typescript\nimport Mode, {useField} from './Model';\n\nexport default class extends Model {\n    @useField()\n    public login?: string;\n\n    @useField('hashable')\n    public password?: string;\n\n    ...\n}\n```\n\n#### Table name\nIn default your model class expects that the name of table is lowercased of its name and followed by letters `s` that refers to plural of that model. When there are more capitalized letters in the name of model, then each words are joined by a symbol `_`. So if we have a model class `UserToken` then we should also have a table with name `user_tokens`. \n\nHowever table name can also be overriden by your own name by overriding static member `tableName` of class `Model` like this:\n```typescript\nimport Model from './Model';\n\nexport default class User extends Model {\n    protected statit tableName: string = 'user_table';\n\n    ...\n}\n```\n#### Inserting data\nYou can insert new data with your model either creating new instance of your model with given data and then save it with method `save()` or using a static method `create(data: ModelProperties)` that will create a new instance and save data to database.\n\n##### Fill your model with data\nTo fill data of your model you can set properties individually:\n```typescript\nconst user = new User();\nuser.first_name = 'John';\nuser.last_name = 'Doe';\n...\n```\nOr you can fill them massively using object of type `ModelProperties` and pass it as data to method `fill(data: ModelProperties)`. First you will need in your model class override a static member variable `fillable` to ensure only your defined fields can be stored. Like this:\n```typescript\nimport Model from './Model';\n\nexport default class User extends Model {\n    \n    protected static fillable: string[] = [\n        'first_name',\n        'last_name'\n    ]\n    ...\n}\n```\nAnd then you can use method `fill`\n```typescript\nconst user = new User()\nuser.fill({\n    first_name: 'John',\n    // other values ...\n})\n```\n##### Using instance method `save`\nInserting data with creating an instance, filling instance with given data and then manually saving them can be done like this:\n```typescript\nconst user = new User()\nuser.fill({\n    first_name: 'John',\n    last_name: 'Doe'\n});\nawait user.save();\n```\n\n##### Using static method `create`\nYou can insert data into table using static method `create(data: ModelProperties)` that that internally create instance, fill data with newly created instance and at the end it saves data into corresponding table. Since it's filling data massively then it is necessary set up `fillable` so it can store all your required values.\n```typescript\n...\nUser.create({\n    first_name: 'John',\n    last_name: 'Doe'\n});\n```\n#### Accessing data\nTo access your model data you can use static methods for finding one instance with its id, find more instances with an array of ids or access all instances. If you want access specific field for your model you have to override type with keyword `as`.\n```typescript\nimport User from './models/User';\n\n...\n// one instance\nconst user = await User.find(1) as User;\n\n// many instances\nconst usersMany = await User.findMany([1, 2]);\n\n// all instances\nconst usersAll = await User.get();\n```\n#### Manipulating data\nYou can update fields of your model or delete whole record. But you can't change `id` property because data are in corresponding tables identified by primary key`id`.\n##### Update data\n```typescript\n...\n\nconst user = await User.find(1) as User;\nuser.first_name = 'Jane';\nwait user.save();\n```\n##### Delete data\nYou can delete data with model by using instance method `destroy` that delete data from database but instance of model still exists.\n```typescript\n...\nconst user = User.find(1) as User;\nawait user.destroy();\n```\n\nOr by using static methods for deleting data by their ids with `delete(id: number)` and `deleteMany(ids: number[])`.\n```typescript\n// Delete one specific user\nawait User.delete(1);\n\n// Delete more users\nawait User.deleteMany([1, 2, 3]);\n```\n### Controllers\nWhen we want to trigger a function either from web or API we can use controllers. Methods of controller can be binded with routes in file `config.ts`. So when route is matched then it can trigger an action (controller's method) and run some of your code.\n\n#### Creating your controller\nFirst you will need to create you controller class in `controllers` folder that will be extending from `class Controller` with some methods.\n\nIt is recommended to use action like this:\n- get list of resources with action `index()`\n- get a specific resource with action `get()`\n- create a new resource with action `create()`\n- update a specific resource with action `update()`\n- delete a specific resource with action `delete()`\n\nBut feel free to use what works better for you.\n\n```typescript\nimport { Request, Response } from 'express'\nimport Controller from \"./Controller\";\n\nexport default class IndexController extends Controller {\n    async index() {\n        this.response.send('\u003ch1\u003eHome\u003c/h1\u003e');\n    }\n}\n```\n\nAll controllers located in directory `controllers` are automatically loaded during app initialization. To ensure that requests are handled by appropriate controller it is necessary to bind route with controller in `config.ts` file. \n\nWhen route path is matched then controller dispatcher creates appropriate controller and dispatch action to handle incoming request. Each route needs to specified HTTP method that are using, controller key and controller action to which is route binded.\n\n##### Controller key\nController key is automatically generated by controller dispatcher and ked is based on combination of folders from `controllers` directory and name of controller. In example if `UserController` is located in directory `controllers/Backend/UserController.ts` then its key will be `Backend/UserController`.\n\n##### Controller action\nController action is name of controller method that should handle incoming request.\n\n##### Binding routes to controller\n```typescript\n...\nconst config: AppConfig = {\n    ...\n    routes: [\n        { \n            path: '/backend/users', \n            method: 'GET', \n            controller: 'Backend/UserController', \n            action: 'index'\n        }\n    ]\n}\n```\n\n##### Request and Response\nEach controller offers properties for accessing request data through property `request` and sending response data to user through property `response`.\n###### Request\nIn `request` proeprty you can find information about processed request such as url, query, params, body, headers or session data.\n###### Response\nWith `response` property you can manipulate what can be send in response. You can set up headers, cookies, content and subsequently send response with status code.\nIf you are using render engine you can also render template view by using method `render`.\n### Authentification\nIf you want to authenticate user you can use `Authenticator` that can be accessed from each controller instance through property `this.auth`. Using authenticator you can login and logout user, get authenticated user or checked if he was already authenticated.\n#### Authenticate user\n```typescript\nimport Controller, {ContainerDI} from './Controller';\n\nexport default class UserController extends Controller {\n    ...\n    async index() {\n        await this.auth.login({\n            login: '\u003cUSER LOGIN\u003e',\n            password: '\u003cUSER PASWORD\u003e'\n        });\n        ...\n    }\n}\n```\n\n#### Check if user is authenticated\n```typescript\nimport Controller, {ContainerDI} from './Controller';\n\nexport default class UserController extends Controller {\n    ...\n    async index() {\n        if (await this.auth.isAuthenticated()) {\n            // some auth logic\n        }\n        ...\n    }\n}\n```\n\n#### Get authenticated user\n```typescript\nimport Controller, {ContainerDI} from './Controller';\n\nexport default class UserController extends Controller {\n    ...\n    async index() {\n        const user = await this.auth.getUser();\n        ...\n    }\n}\n```\n\n#### Logout user\n```typescript\nimport Controller, {ContainerDI} from './Controller';\n\nexport default class UserController extends Controller {\n    ...\n    async index() {\n        await this.auth.logout();\n        ...\n    }\n}\n```\n\n### Services\nYour business logic shouldn't be used directly in controllers so you could create your own services and then using them in your controllers. \n\n#### Creating your service\nFirst you need to create your service class in `services` folder that will be implmenting `interface Service` with some methods that can be ran in your controller.\n```typescript\nimport {Service} from '../interfaces';\n...\nexport default class UserService implements Service {\n    ...\n    async getUser(): Promise\u003cUser\u003e {\n        ...\n        return user;\n    }\n}\n```\n#### Using your service\nAll services located in directory `services` are automatically loaded during app initialization. If your service class implements `interface Service` it also implements `interface Injectable` and services that are injectable can be automatically injected in your controller contructor through parameter `container: ContainerDI` or later in your actions through property `container`. \n\nTo ensure you can use a container with automatically injected services it is necessary to add parameter `container: ContainerDI` to your contructor. And for accessing property `container` you have to pass `container` parameter to parent `class Controller` by calling parent contstructor `super(container)`.\n```typescript\nimport Controller, {ContainerDI} from './Controller';\nimport {UserService} from '../services';\n\nexport default class UserController extends Controller {\n    private userService: UserService;\n    ...\n\n    constructor(container: ContainerDI) {\n        super(container);\n\n        userService = container.userService as UserService;\n    }\n\n    ...\n\n    async index() {\n        this.response.render('users', await userService.getAll());\n    }\n}\n\n```\n### Dependency Injection Container\nAll components that implements `interface Injecable` are automatically injected into `ContainerDI` and components can be references by name that is pascal case format in example `UserService` can be referenced using name `container.userService`. \n\nIf a component is namespaced with some folders then folder names are prepended in fron of component name so for example `Backend/UserService` should be referenced using name `container.backendUserService`.\n### Views\nFor the rendering views as response from controller application uses [LiquidJS](https://liquidjs.com) template engine. [LiquidJS](https://liquidjs.com) uses own file types `.liquid` for templates that supports HTML. In template you can use many helper functions such as conditions, for loops, using variables and so on. Detailed information how to use helpers function you can find [on their website](https://liquidjs.com/tags/overview.html).\n#### Rendering template file\nFirst you will need to create a new template file in folder `views` with file type `.liquid` in example `home.liquid`.\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n    \u003ctitle\u003eHome\u003c/title\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch1\u003eHome\u003c/h1\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\nThen you can render your template file in your controller method with controller property `response` and use its method `render` to render your template file. It is necessary to pass name of your template file that you created in `views` folder.\n```typescript\nimport Controller from \"./Controller\";\n\nexport default class IndexController extends Controller {\n    async index() {\n        this.response.render('home');\n    }\n}\n```\n### Error handling\nThrown errors are handled on level above currently processed controller so all error should be caught and currently showing placeholder error content.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaderarasto%2Fts-nodejs-mvc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaderarasto%2Fts-nodejs-mvc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaderarasto%2Fts-nodejs-mvc/lists"}