# loopback-connector-mysql [MySQL](https://www.mysql.com/) is a popular open-source relational database management system (RDBMS). The `loopback-connector-mysql` module provides the MySQL connector module for the LoopBack framework. ## Installation In your application root directory, enter this command to install the connector: ```sh npm install loopback-connector-mysql --save ``` **Note**: The MySQL connector requires MySQL 5.0+. This installs the module from npm and adds it as a dependency to the application's `package.json` file. If you create a MySQL data source using the data source generator as described below, you don't have to do this, since the generator will run `npm install` for you. ## Creating a MySQL data source For LoopBack 4 users, use the LoopBack 4 [Command-line interface](https://loopback.io/doc/en/lb4/Command-line-interface.html) to generate a DataSource with MySQL connector to your LB4 application. Run [`lb4 datasource`](https://loopback.io/doc/en/lb4/DataSource-generator.html), it will prompt for configurations such as host, post, etc. that are required to connect to a MySQL database. After setting it up, the configuration can be found under `src/datasources/<DataSourceName>.datasource.ts`, which would look like this: ```ts const config = { name: 'db', connector: 'mysql', url: '', host: 'localhost', port: 3306, user: 'user', password: 'pass', database: 'testdb', }; ``` <details><summary markdown="span"><strong>For LoopBack 3 users</strong></summary> Use the [Data source generator](http://loopback.io/doc/en/lb3/Data-source-generator.html) to add a MySQL data source to your application. The generator will prompt for the database server hostname, port, and other settings required to connect to a MySQL database. It will also run the `npm install` command above for you. The entry in the application's `/server/datasources.json` will look like this: ```javascript "mydb": { "name": "mydb", "connector": "mysql", "host": "myserver", "port": 3306, "database": "mydb", "password": "mypassword", "user": "admin" } ``` </details> Edit `<DataSourceName>.datasources.ts` to add any other additional properties that you require. ### Properties <table> <thead> <tr> <th width="150">Property</th> <th width="80">Type</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>collation</td> <td>String</td> <td>Determines the charset for the connection. Default is utf8_general_ci.</td> </tr> <tr> <td>connector</td> <td>String</td> <td>Connector name, either “loopback-connector-mysql” or “mysql”.</td> </tr> <tr> <td>connectionLimit</td> <td>Number</td> <td>The maximum number of connections to create at once. Default is 10.</td> </tr> <tr> <td>database</td> <td>String</td> <td>Database name</td> </tr> <tr> <td>debug</td> <td>Boolean</td> <td>If true, turn on verbose mode to debug database queries and lifecycle.</td> </tr> <tr> <td>host</td> <td>String</td> <td>Database host name</td> </tr> <tr> <td>password</td> <td>String</td> <td>Password to connect to database</td> </tr> <tr> <td>port</td> <td>Number</td> <td>Database TCP port</td> </tr> <tr> <td>socketPath</td> <td>String</td> <td>The path to a unix domain socket to connect to. When used host and port are ignored.</td> </tr> <tr> <td>supportBigNumbers</td> <td>Boolean</td> <td>Enable this option to deal with big numbers (BIGINT and DECIMAL columns) in the database. Default is false.</td> </tr> <tr> <td>timeZone</td> <td>String</td> <td>The timezone used to store local dates. Default is ‘local’.</td> </tr> <tr> <td>url</td> <td>String</td> <td>Connection URL of form <code>mysql://user:password@host/db</code>. Overrides other connection settings.</td> </tr> <tr> <td>username</td> <td>String</td> <td>Username to connect to database</td> </tr> <tr> <td>allowExtendedOperators</td> <td>Boolean</td> <td>Set to <code>true</code> to enable MySQL-specific operators such as <code>match</code>. Learn more in <a href="#extended-operators">Extended operators</a> below. </td> </tr> </tbody> </table> **NOTE**: In addition to these properties, you can use additional parameters supported by [`node-mysql`](https://github.com/felixge/node-mysql). ## Type mappings See [LoopBack 4 types](http://loopback.io/doc/en/lb4/LoopBack-types.html) (or [LoopBack 3 types](http://loopback.io/doc/en/lb3/LoopBack-types.html)) for details on LoopBack's data types. ### LoopBack to MySQL types <table> <thead> <tr> <th width="450">LoopBack Type</th> <th width="450">MySQL Type</th> </tr> </thead> <tbody> <tr> <td>String/JSON</td> <td>VARCHAR</td> </tr> <tr> <td>Text</td> <td>TEXT</td> </tr> <tr> <td>Number</td> <td>INT</td> </tr> <tr> <td>Date</td> <td>DATETIME</td> </tr> <tr> <td>Boolean</td> <td>TINYINT(1)</td> </tr> <tr> <td><a href="http://apidocs.strongloop.com/loopback-datasource-juggler/#geopoint" class="external-link">GeoPoint</a> object</td> <td>POINT</td> </tr> <tr> <td>Custom Enum type<br>(See <a href="#enum">Enum</a> below)</td> <td>ENUM</td> </tr> </tbody> </table> ### MySQL to LoopBack types <table> <thead> <tr> <th width="450">MySQL Type</th> <th width="450">LoopBack Type</th> </tr> </thead> <tbody> <tr> <td>CHAR</td> <td>String</td> </tr> <tr> <td>BIT(1)<br>CHAR(1)<br>TINYINT(1)</td> <td>Boolean</td> </tr> <tr> <td>VARCHAR<br>TINYTEXT<br>MEDIUMTEXT<br>LONGTEXT<br>TEXT<br>ENUM<br>SET</td> <td>String</td> </tr> <tr> <td>TINYBLOB<br>MEDIUMBLOB<br>LONGBLOB<br>BLOB<br>BINARY<br>VARBINARY<br>BIT</td> <td>Node.js <a href="http://nodejs.org/api/buffer.html">Buffer object</a></td> </tr> <tr> <td>TINYINT<br>SMALLINT<br>INT<br>MEDIUMINT<br>YEAR<br>FLOAT<br>DOUBLE<br>NUMERIC<br>DECIMAL</td> <td> <p>Number<br>For FLOAT and DOUBLE, see <a href="#floating-point-types">Floating-point types</a>. </p> <p>For NUMERIC and DECIMAL, see <a href="MySQL-connector.html">Fixed-point exact value types</a></p> </td> </tr> <tr> <td>DATE<br>TIMESTAMP<br>DATETIME</td> <td>Date</td> </tr> </tbody> </table> _NOTE_ as of v3.0.0 of MySQL Connector, the following flags were introduced: - `treatCHAR1AsString` default `false` - treats CHAR(1) as a String instead of a Boolean - `treatBIT1AsBit` default `true` - treats BIT(1) as a Boolean instead of a Binary - `treatTINYINT1AsTinyInt` default `true` - treats TINYINT(1) as a Boolean instead of a Number ## Data mapping properties Except the common database-specific properties we introduce in [How LoopBack Models Map To Database Tables/Collections](https://loopback.io/doc/en/lb4/Model.html#how-loopback-models-map-to-database-tablescollections), the following are more detailed examples and MySQL-specific settings. ### Table/Column Names Besides the basic LoopBack types, as we introduced above, you can also specify additional MySQL-specific properties for a LoopBack model. It would be mapped to the database. Use the `mysql.<property>` in the model definition or the property definition to configure the table/column definition. For example, the following settings would allow you to have custom table name (`Custom_User`) and column name (`custom_id` and `custom_name`). Such mapping is useful when you'd like to have different table/column names from the model: {% include code-caption.html content="user.model.ts" %} ```ts @model({ settings: { mysql: { schema: 'testdb', table: 'Custom_User'} }, }) export class User extends Entity { @property({ type: 'number', required: true, id: true, mysql: { columnName: 'custom_id', }, }) id: number; @property({ type: 'string', mysql: { columnName: 'custom_name', }, }) name?: string; ``` <details><summary markdown="span"><strong>For LoopBack 3 users</strong></summary> ```javascript { "name": "User", "options": { "mysql": { "schema": "testdb", "table": "Custom_User" } }, "properties": { "id": { "type": "Number", "required": true, "mysql": { "columnName": "custom_id", } }, "name": { "type": "String", "mysql": { "columnName": "custom_name", } }, } } ``` </details> ### Numeric Types Except the names, you can also use the dataType column/property attribute to specify what MySQL column type to use. The following MySQL type-dataType combinations are supported: - number - integer - tinyint - smallint - mediumint - int - bigint - float - double - decimal The following examples will be in LoopBack 4 style, but it's the same if you provide `mysql.<property>` to the LB3 property definition. #### Floating-point types For Float and Double data types, use the `precision` and `scale` options to specify custom precision. Default is (16,8). <details><summary markdown="span"><strong>Example</strong></summary> ```ts @property({ type: 'Number', mysql: { dataType: 'float', precision: 20, scale: 4 } }) price: Number; ``` </details> #### Fixed-point exact value types For Decimal and Numeric types, use the `precision` and `scale` options to specify custom precision. Default is (9,2). These aren't likely to function as true fixed-point. <details><summary markdown="span"><strong>Example</strong></summary> ```ts @property({ type: 'Number', mysql: { dataType: 'decimal', precision: 12, scale: 8 } }) price: Number; ``` </details> ### Text types Convert String / DataSource.Text / DataSource.JSON to the following MySQL types: - varchar - char - text - mediumtext - tinytext - longtext <details><summary markdown="span"><strong>Example</strong></summary> ```ts @property({ type: 'String', mysql: { dataType: 'char', dataLength: 24 // limits the property length }, }) userName: String; ``` </details> ### Dat types Convert JSON Date types to datetime or timestamp. <details><summary markdown="span"><strong>Example</strong></summary> ```ts @property({ type: 'Date', mysql: { dataType: 'timestamp', }, }) startTime: Date; ``` </details> ### Enum See the [Model ENUM property](https://loopback.io/doc/en/lb4/Model.html#enum-property) for details. ### Default Clause/Constant Use the `default` and `dataType` properties to have MySQL handle **setting column `DEFAULT` value**. <details><summary markdown="span"><strong>Example</strong></summary> ```ts @property({ type: 'String', mysql: { dataType: 'varchar', default: 'pending' } }) status: String; @property({ type: 'Number', mysql: { dataType: 'int', default: 42 } }) maxDays: Number; @property({ type: 'boolean', mysql: { dataType: 'tinyint', default: 1 } }) isDone: Boolean; ``` </details> For the date or timestamp types use `CURRENT_TIMESTAMP` or `now`. <details><summary markdown="span"><strong>Example</strong></summary> ```ts @property({ type: 'Date', mysql: { dataType: 'datetime', default: 'CURRENT_TIMESTAMP' } }) last_modified: Date; ``` </details> **NOTE**: The following column types do **NOT** supported [MySQL Default Values](https://dev.mysql.com/doc/refman/5.7/en/data-type-defaults.html): - BLOB - TEXT - GEOMETRY - JSON ## Extended operators MySQL connector supports the following MySQL-specific operators: - [`match`](#operator-match) Please note extended operators are disabled by default, you must enable them at datasource level or model level by setting `allowExtendedOperators` to `true`. ### Operator `match` The `match` operator allows you to perform a full text search using the [MATCH() .. AGAINST()](https://dev.mysql.com/doc/refman/8.0/en/fulltext-search.html) operator in MySQL. Three different modes of the `MATCH` clause are also available in the form of operators - - `matchbool` for [Boolean Full Text Search](https://dev.mysql.com/doc/refman/8.0/en/fulltext-boolean.html) - `matchnl` for [Natural Language Full Text Search](https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html) - `matchqe` for [Full-Text Searches with Query Expansion](https://dev.mysql.com/doc/refman/8.0/en/fulltext-query-expansion.html) - `matchnlqe` for [Full-Text Searches with Query Expansion](https://dev.mysql.com/doc/refman/8.0/en/fulltext-query-expansion.html) with the `IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION` modifier. By default, the `match` operator works in Natural Language mode. **Note** The fields you are querying must be setup with a `FULLTEXT` index to perform full text search on them. Assuming a model such as this: ```ts @model({ settings: { allowExtendedOperators: true, } }) class Post { @property({ type: 'string', mysql: { index: { kind: 'FULLTEXT' } }, }) content: string; } ``` You can query the content field as follows: ```ts const posts = await postRepository.find({ where: { { content: {match: 'someString'}, } } }); ``` ## Discovery and auto-migration ### Model discovery The MySQL connector supports _model discovery_ that enables you to create LoopBack models based on an existing database schema. Once you defined your datasource: - LoopBack 4 users could use the commend [`lb4 discover`](https://loopback.io/doc/en/lb4/Discovering-models.html) to discover models. - For LB3 users, please check [Discovering models from relational databases](https://loopback.io/doc/en/lb3/Discovering-models-from-relational-databases.html). (See [database discovery API](http://apidocs.strongloop.com/loopback-datasource-juggler/#datasource-prototype-discoverandbuildmodels) for related APIs information) ### Auto-migration The MySQL connector also supports _auto-migration_ that enables you to create a database schema from LoopBack models. For example, based on the following model, the auto-migration method would create/alter existing `Customer` table in the database. Table `Customer` would have two columns: `name` and `id`, where `id` is also the primary key that has `auto_increment` set as it has definition of `type: 'Number'` and `generated: true`: ```ts @model() export class Customer extends Entity { @property({ id: true, type: 'Number', generated: true, }) id: number; @property({ type: 'string', }) name: string; } ``` Moreover, additional MySQL-specific properties mentioned in the [Data mapping properties](#data-mapping-properties) section work with auto-migration as well. #### Auto-generated ids For now LoopBack MySQL connector only supports auto-generated id (`generated: true`) for integer type as for MySQL, the default id type is _integer_. If you'd like to use other types such as string (uuid) as the id type, you can: - use uuid that is **generated by your LB application** by setting [`defaultFn: uuid`](https://loopback.io/doc/en/lb4/Model.html#property-decorator). ```ts @property({ id: true, type: 'string' defaultFn: 'uuidv4', // generated: true, -> not needed }) id: string; ``` - Alter the table in your database to use a certain function if you prefer having **the database to generate the value**. ```ts @property({ id: true, type: 'string' generated: true, // to indicate the value generates by the db useDefaultIdType: false, // needed }) id: string; ``` #### Auto-migrate/Auto-update models with foreign keys Foreign key constraints can be defined in the model definition. **Note**: The order of table creation is important. A referenced table must exist before creating a foreign key constraint. The order can be specified using the optional <a href="https://loopback.io/doc/en/lb4/apidocs.repository.schemamigrationoptions.html">`SchemaMigrationOptions`</a> argument of `migrateSchema`: ``` await app.migrateSchema({ models: [ 'Customer', 'Order' ] }); ``` Define your models and the foreign key constraints as follows: {% include code-caption.html content="customer.model.ts" %} ```ts @model() export class Customer extends Entity { @property({ id: true, type: 'Number', generated: true, }) id: number; @property({ type: 'string', }) name: string; } ``` `order.model.ts`: ```ts @model({ settings: { foreignKeys: { fk_order_customerId: { name: 'fk_order_customerId', entity: 'Customer', entityKey: 'id', foreignKey: 'customerId', }, }, }) export class Order extends Entity { @property({ id: true, type: 'Number', generated: true }) id: number; @property({ type: 'string' }) name: string; @property({ type: 'Number' }) customerId: number; } ``` <details><summary markdown="span"><strong>For LoopBack 3 users</strong></summary> ```json ({ "name": "Customer", "options": { "idInjection": false }, "properties": { "id": { "type": "Number", "id": 1 }, "name": { "type": "String", "required": false } } }, { "name": "Order", "options": { "idInjection": false, "foreignKeys": { "fk_order_customerId": { "name": "fk_order_customerId", "entity": "Customer", "entityKey": "id", "foreignKey": "customerId" } } }, "properties": { "id": { "type": "Number" "id": 1 }, "customerId": { "type": "Number" }, "description": { "type": "String", "required": false } } }) ``` </details> MySQL handles the foreign key integrity by the referential action specified by `ON UPDATE` and `ON DELETE`. You can specify which referential actions the foreign key follows in the model definition upon auto-migrate or auto-update operation. Both `onDelete` and `onUpdate` default to `restrict`. Take the example we showed above, let's add the referential action to the foreign key `customerId`: ```ts @model({ settings: { foreignKeys: { fk_order_customerId: { name: 'fk_order_customerId', entity: 'Customer', entityKey: 'id', foreignKey: 'customerId', onUpdate: 'restrict', // restrict|cascade|set null|no action|set default onDelete: 'cascade' // restrict|cascade|set null|no action|set default }, }, }) export class Order extends Entity { ... ``` <details><summary markdown="span"><strong>For LoopBack 3 users</strong></summary> **model-definiton.json** ```json { "name": "Customer", "options": { "idInjection": false }, "properties": { "id": { "type": "Number", "id": 1 }, "name": { "type": "String", "required": false } } }, { "name": "Order", "options": { "idInjection": false, "foreignKeys": { "fk_order_customerId": { "name": "fk_order_customerId", "entity": "Customer", "entityKey": "id", "foreignKey": "customerId", "onUpdate": "restrict", "onDelete": "cascade" } } }, "properties": { "id": { "type": "Number" "id": 1 }, "customerId": { "type": "Number" }, "description": { "type": "String", "required": false } } } ``` **boot-script.js** ```js module.exports = function (app) { var mysqlDs = app.dataSources.mysqlDS; var Book = app.models.Order; var Author = app.models.Customer; // first autoupdate the `Customer` model to avoid foreign key constraint failure mysqlDs.autoupdate('Customer', function (err) { if (err) throw err; console.log('\nAutoupdated table `Customer`.'); mysqlDs.autoupdate('Order', function (err) { if (err) throw err; console.log('\nAutoupdated table `Order`.'); // at this point the database table `Order` should have one foreign key `customerId` integrated }); }); }; ``` </details> #### Breaking Changes with GeoPoint since 5.x Prior to `loopback-connector-mysql@5.x`, MySQL connector was saving and loading GeoPoint properties from the MySQL database in reverse. MySQL expects values to be `POINT(X, Y)` or `POINT(lng, lat)`, but the connector was saving them in the opposite order(i.e. `POINT(lat,lng)`). Use the `geopoint` type to achieve so: ```ts @property({ type: 'geopoint' }) name: GeoPoint; ``` If you have an application with a model that has a GeoPoint property using previous versions of this connector, you can migrate your models using the following programmatic approach: <details><summary markdown="span"><strong>Click here to expand</strong></summary> **NOTE** Please back up the database tables that have your application data before performing any of the steps. 1. Create a boot script under `server/boot/` directory with the following: ```js 'use strict'; module.exports = function (app) { function findAndUpdate() { var teashop = app.models.teashop; //find all instances of the model we'd like to migrate teashop.find({}, function (err, teashops) { teashops.forEach(function (teashopInstance) { //what we fetch back from the db is wrong, so need to revert it here var newLocation = { lng: teashopInstance.location.lat, lat: teashopInstance.location.lng, }; //only update the GeoPoint property for the model teashopInstance.updateAttribute('location', newLocation, function ( err, inst, ) { if (err) console.log('update attribute failed', err); else console.log('updateAttribute successful'); }); }); }); } findAndUpdate(); }; ``` 2. Run the boot script by simply running your application or `node .` For the above example, the model definition is as follows: ```json { "name": "teashop", "base": "PersistedModel", "idInjection": true, "options": { "validateUpsert": true }, "properties": { "name": { "type": "string", "default": "storename" }, "location": { "type": "geopoint" } }, "validations": [], "relations": {}, "acls": [], "methods": {} } ``` </details> ## Running tests ### Own instance If you have a local or remote MySQL instance and would like to use that to run the test suite, use the following command: - Linux ```bash MYSQL_HOST=<HOST> MYSQL_PORT=<PORT> MYSQL_USER=<USER> MYSQL_PASSWORD=<PASSWORD> MYSQL_DATABASE=<DATABASE> CI=true npm test ``` - Windows ```bash SET MYSQL_HOST=<HOST> SET MYSQL_PORT=<PORT> SET MYSQL_USER=<USER> SET MYSQL_PASSWORD=<PASSWORD> SET MYSQL_DATABASE=<DATABASE> SET CI=true npm test ``` ### Docker If you do not have a local MySQL instance, you can also run the test suite with very minimal requirements. - Assuming you have [Docker](https://docs.docker.com/engine/installation/) installed, run the following script which would spawn a MySQL instance on your local: ```bash source setup.sh <HOST> <PORT> <USER> <PASSWORD> <DATABASE> ``` where `<HOST>`, `<PORT>`, `<USER>`, `<PASSWORD>` and `<DATABASE>` are optional parameters. The default values are `localhost`, `3306`, `root`, `pass` and `testdb` respectively. - Run the test: ```bash npm test ```