![npm version](https://img.shields.io/node/v/xmysql.svg) [![Build Status](https://travis-ci.org/o1lab/xmysql.svg?branch=master)](https://travis-ci.org/o1lab/xmysql) [![GitHub stars](https://img.shields.io/github/stars/o1lab/xmysql.svg?style=plastic)](https://github.com/o1lab/xmysql/stargazers) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/o1lab/xmysql/master/LICENSE) # Xmysql : One command to generate REST APIs for any MySql database # Why this ?

xmysql gif

Generating REST APIs for a MySql database which does not follow conventions of frameworks such as rails, django, laravel etc is a small adventure that one like to avoid .. Hence this. # Setup and Usage ``` npm install -g xmysql ``` ``` xmysql -h localhost -u mysqlUsername -p mysqlPassword -d databaseName ``` ``` http://localhost:3000 ```
That is it! Happy hackery! # Example : Generate REST APIs for [Magento](http://www.magereverse.com/index/magento-sql-structure/version/1-7-0-2) Powered by popular node packages : ([express](https://github.com/expressjs/express), [mysql](https://github.com/mysqljs/mysql)) => { [xmysql](https://github.com/o1lab/xmysql) }

xmysql gif

# Features * Generates API for **ANY** MySql database * :fire::fire: Serves APIs irrespective of naming conventions of primary keys, foreign keys, tables etc * Support for composite primary keys * CRUD, List, FindOne, Count, Exists * Relations * Pagination * Sorting * :fire: Column filtering - Fields * :fire: Row filtering - Where * Aggregate functions * :fire::fire: Group By, Having (as query params) * :fire::fire: Group By, Having (as a separate API) * :fire::fire: Multiple group by in one API * :fire::fire: Chart API for numeric column * Prototyping (features available with ONLY local MySql server) * Run dynamic queries :fire::fire::fire: * Upload single file * Upload multiple files * Download file Use HTTP clients like [Postman](https://www.getpostman.com/) or [similar tools](https://chrome.google.com/webstore/search/http%20client?_category=apps) to invoke REST API calls ____ Download [node](https://nodejs.org/en/download/current/), [mysql](https://dev.mysql.com/downloads/mysql/) [(setup mysql)](https://dev.mysql.com/doc/mysql-getting-started/en/#mysql-getting-started-installing), [sample database](https://dev.mysql.com/doc/employee/en/employees-installation.html) - if you haven't on your system. ## Usual Suspects in a REST API framework | HTTP Type | API URL | Comments | |-----------|----------------------------------|--------------------------------------------------------- | GET | / | Gets all REST APIs | | GET | /api/tableName | Lists rows of table | | POST | /api/tableName | Create a new row | | PUT | /api/tableName | Replaces existing row with new row | | GET | /api/tableName/:id | Retrieves a row by primary key | | PATCH | /api/tableName/:id | Updates a row by primary key | | GET | /api/tableName/findOne | Works as list but gets single record matching criteria | | GET | /api/tableName/count | Count number of rows in a table | | GET | /api/tableName/:id/exists | True or false whether a row exists or not | | DELETE | /api/tableName/:id | Delete a row by primary key | | GET | /api/parentTable/:id/childTable | Get list of child table with paren table foreign key | ## APIs with HOT features | HTTP Type | API URL | Comments | |-----------|----------------------------------|--------------------------------------------------------- | GET | /api/tableName/aggregate | Aggregate results of numeric column(s) | | GET :fire:| /api/tableName/groupby | Group by results of column(s) | | GET :fire:| /api/tableName/ugroupby | Multiple group by results using one call | | GET :fire:| /api/tableName/chart | Numeric column distribution based on (min,max,step) or(step array) or (automagic)| ## APIs for Prototyping :snowboarder: :snowboarder: | HTTP Type | API URL | Comments | |-----------|----------------------------------|--------------------------------------------------------- | GET :fire:| /dynamic | execute dynamic mysql statements with params | | GET :fire:| /upload | upload single file | | GET :fire:| /uploads | upload multiple files | | GET :fire:| /download | download a file | | GET :fire:| /api/tableName/describe | describe each table for its columns | | GET :fire:| /api/tables | get all tables in database | ## Relational Tables xmysql identifies foreign key relations automatically and provides GET api. ``` /api/blogs/103/comments ``` eg: blogs is parent table and comments is child table. API invocation will result in all comments for blog primary key 103. ## Support for composite primary keys #### ___ (three underscores) ``` /api/payments/103___JM555205 ``` *___* : If there are multiple primary keys - seperate them by three underscores as shown ## Pagination #### _p & _size _p indicates page and _size indicates size of response rows By default 20 records and max of 100 are returned per GET request on a table. ``` /api/payments?_size=50 ``` ``` /api/payments?_p=2 ``` ``` /api/payments?_p=2&_size=50 ``` ## Order by / Sorting #### ASC ``` /api/payments?_sort=column1 ``` eg: sorts ascending by column1 #### DESC ``` /api/payments?_sort=-column1 ``` eg: sorts descending by column1 #### Multiple fields in sort ``` /api/payments?_sort=column1,-column2 ``` eg: sorts ascending by column1 and descending by column2 ## Column filtering / Fields ``` /api/payments?_fields=customerNumber,checkNumber ``` eg: gets only customerNumber and checkNumber in response of each record ``` /api/payments?_fields=-checkNumber ``` eg: gets all fields in table row but not checkNumber ## Row filtering / Where #### Comparison operators ``` eq - '=' ne - '!=' gt - '>' gte - '>=' lt - '<' lte - '<=' ``` #### Use of comparison operators ``` /api/payments?_where=(checkNumber,eq,JM555205) ``` #### Logical operators ``` ~or - 'or' ~and - 'and' ~xor - 'xor' ``` #### Use of logical operators eg: simple logical expression ``` /api/payments?_where=(checkNumber,eq,JM555205)~or(checkNumber,eq,OM314933) ``` eg: complex logical expression ``` /api/payments?_where=((checkNumber,eq,JM555205)~or(checkNumber,eq,OM314933))~and(amount,gt,100) ``` eg: logical expression with sorting(_sort), pagination(_p), column filtering (_fields) ``` /api/payments?_where=(amount,gte,1000)&_sort=-amount&p=2&&_fields=customerNumber ``` eg: filter of rows using _where is available for relational route URLs too. ``` /api/offices/1/employees?_where=(jobTitle,eq,Sales%20Rep) ``` ## FindOne ``` /api/tableName/findOne?_where=(id,eq,1) ``` Works similar to list but only returns top/one result. Used in conjunction with _where ## Count ``` /api/tableName/count ``` Returns number of rows in table ## Exists ``` /api/tableName/1/exists ``` Returns true or false depending on whether record exists ## Group By, Having (as query params) ``` /api/offices?_groupby=country ``` eg: SELECT country,count(*) FROM offices GROUP BY country ``` /api/offices?_groupby=country&_having=(_count,gt,1) ``` eg: SELECT country,count(1) as _count FROM offices GROUP BY country having _count > 1 ## Group By, Having (as a seperate route) ``` /api/offices/groupby?_fields=country ``` eg: SELECT country,count(*) FROM offices GROUP BY country ``` /api/offices/groupby?_fields=country,city ``` eg: SELECT country,city,count(*) FROM offices GROUP BY country,city ``` /api/offices/groupby?_fields=country,city&_having=(_count,gt,1) ``` eg: SELECT country,city,count(*) as _count FROM offices GROUP BY country,city having _count > 1 ## Group By, Order By ``` /api/offices/groupby?_fields=country,city&sort=city ``` eg: SELECT country,city,count(*) FROM offices GROUP BY country,city ORDER BY city ASC ``` /api/offices/groupby?_fields=country,city&sort=city,country ``` eg: SELECT country,city,count(*) FROM offices GROUP BY country,city ORDER BY city ASC, country ASC ``` /api/offices/groupby?_fields=country,city&sort=city,-country ``` eg: SELECT country,city,count(*) FROM offices GROUP BY country,city ORDER BY city ASC, country DESC ## Aggregate functions ``` http://localhost:3000/api/payments/aggregate?_fields=amount response body [ { "min_of_amount": 615.45, "max_of_amount": 120166.58, "avg_of_amount": 32431.645531, "sum_of_amount": 8853839.23, "stddev_of_amount": 20958.625377426568, "variance_of_amount": 439263977.71130896 } ] ``` eg: retrieves all numeric aggregate of a column in a table ``` http://localhost:3000/api/orderDetails/aggregate?_fields=priceEach,quantityOrdered response body [ { "min_of_priceEach": 26.55, "max_of_priceEach": 214.3, "avg_of_priceEach": 90.769499, "sum_of_priceEach": 271945.42, "stddev_of_priceEach": 36.576811252187795, "variance_of_priceEach": 1337.8631213781719, "min_of_quantityOrdered": 6, "max_of_quantityOrdered": 97, "avg_of_quantityOrdered": 35.219, "sum_of_quantityOrdered": 105516, "stddev_of_quantityOrdered": 9.832243813502942, "variance_of_quantityOrdered": 96.67301840816688 } ] ``` eg: retrieves numeric aggregate can be done for multiple columns too ## Union of many group by statements :fire::fire:**[ HOTNESS ALERT ]** Group by multiple columns in one API call using _fields query params - comes really handy ``` http://localhost:3000/api/employees/ugroupby?_fields=jobTitle,reportsTo response body { "jobTitle":[ { "Sales Rep":17 }, { "President":1 }, { "Sale Manager (EMEA)":1 }, { "Sales Manager (APAC)":1 }, { "Sales Manager (NA)":1 }, { "VP Marketing":1 }, { "VP Sales":1 } ], "reportsTo":[ { "1002":2 }, { "1056":4 }, { "1088":3 }, { "1102":6 }, { "1143":6 }, { "1621":1 } { "":1 }, ] } ``` ## Chart :fire::fire: **[ HOTNESS ALERT ]** Chart API returns distribution of a numeric column in a table It comes in three flavours 1. Chart : With min, max, step in query params :fire::fire: This API returns the number of rows where amount is between (0,25000), (25001,50000) ... ``` /api/payments/chart?_fields=amount&min=0&max=131000&step=25000 Response [ { "amount": "0 to 25000", "_count": 107 }, { "amount": "25001 to 50000", "_count": 124 }, { "amount": "50001 to 75000", "_count": 30 }, { "amount": "75001 to 100000", "_count": 7 }, { "amount": "100001 to 125000", "_count": 5 }, { "amount": "125001 to 150000", "_count": 0 } ] ``` 2. Chart : With step array in params :fire::fire: This API returns distribution between the step array specified ``` /api/payments/chart?_fields=amount&steparray=0,10000,20000,70000,140000 Response [ { "amount": "0 to 10000", "_count": 42 }, { "amount": "10001 to 20000", "_count": 36 }, { "amount": "20001 to 70000", "_count": 183 }, { "amount": "70001 to 140000", "_count": 12 } ] ``` 3. Chart : with no params :fire::fire: This API figures out even distribution of a numeric column in table and returns the data ``` /api/payments/chart?_fields=amount Response [ { "amount": "-9860 to 11100", "_count": 45 }, { "amount": "11101 to 32060", "_count": 91 }, { "amount": "32061 to 53020", "_count": 109 }, { "amount": "53021 to 73980", "_count": 16 }, { "amount": "73981 to 94940", "_count": 7 }, { "amount": "94941 to 115900", "_count": 3 }, { "amount": "115901 to 130650", "_count": 2 } ] ``` Please Note: _fields in Chart API can only take numeric column as its argument. ## Run dynamic queries Dynamic queries on a database can be run by POST method to URL localhost:3000/dynamic This is enabled only when using local mysql server i.e -h localhost or -h 127.0.0.1 option. Post body takes two fields : query and params. >query: SQL query or SQL prepared query (ones with ?? and ?) >params : parameters for SQL prepared query ``` POST /dynamic { "query": "select * from ?? limit 1,20", "params": ["customers"] } ``` POST /dynamic URL can have any suffix to it - which can be helpful in prototyping eg: ``` POST /dynamic/weeklyReport ``` ``` POST /dynamic/user/update ``` ## Upload single file ``` POST /upload ``` Do POST operation on /upload url with multiform 'field' assigned to local file to be uploaded eg: curl --form file=@/Users/me/Desktop/a.png http://localhost:3000/upload returns uploaded file name else 'upload failed' (Note: POSTMAN has issues with file uploading hence examples with curl) ## Upload multiple files ``` POST /uploads ``` Do POST operation on /uploads url with multiform 'fields' assigned to local files to be uploaded > Notice 's' near /api/upload**s** and file**s** in below example eg: curl --form files=@/Users/me/Desktop/a.png --form files=@/Users/me/Desktop/b.png http://localhost:3000/uploads returns uploaded file names as string ## Download file http://localhost:3000/download?name=fileName > For upload and download of files -> you can specify storage folder using -s option > Upload and download apis are available only with local mysql server ## When to use ? * You need just REST APIs without much hassle for (ANY) MySql database. * You are learning new frontend frameworks and need REST APIs for your MySql database. * You are working on a demo, hacks etc ## When NOT to use ? * If you are in need of a full blown MVC framework, ACL, Authorisation etc - its early days please watch/star this repo. Thank you. ### Command line options ``` Options: -V, --version output the version number -h, --host hostname of mysql -d, --database database schema name -u, --user username of database / root by default -p, --password password of database / empty by default -n, --portNumber port number / 3000 by default -s, --storageFolder storage folder / current working dir by default -h, --help output usage information Examples: $ xmysql -u username -p password -d databaseSchema ```
# Docker Simply build with `docker build -t xmysql .` and run with `docker run -p 3000:3000 -d xmysql` The best way for testing is to run mysql in a docker container too and create a docker network, so that `xmysql` can access the `mysql` container with a name from docker network. 1. Create network * `docker network create mynet` 2. Start mysql with docker name `some-mysql` and bind to docker network `mynet` * `docker run --name some-mysql -p 3306:3306 --net mynet -e MYSQL_ROOT_PASSWORD=password -d mysql` 3. build xmysql container (if not done yet) * `docker build -t xmysql .` 4. run xmysql and set env variable for `some-mysql` from step 2 * `docker run -p 3000:3000 -d -e DATABASE_HOST=some-mysql --net mynet xmysql` You can also pass the environment variables to a file and use them as an option with docker like `docker run --env-file ./env.list -p 3000:3000 --net mynet -d xmysql` environment variables which can be used: ``` ENV DATABASE_HOST 127.0.0.1 ENV DATABASE_USER root ENV DATABASE_PASSWORD password ENV DATABASE_NAME sakila ``` # Tests : setup on local machine Login to mysql shell ``` mysql> create database classicmodels mysql> use classicmodels mysql> source path_to/xmysql/tests/sample.sql ``` ``` $ mocha tests/*.js --exit ```