├── .gitignore ├── README.md ├── docker-compose.yml ├── package.json ├── server ├── .eslintrc └── index.js └── spec └── index.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .envrc 3 | npm-debug.log 4 | .DS_Store 5 | db/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > Neo4j Docker Node Express REST API 2 | 3 | ![alt tag](http://tech.orteedev.pl/neo4j-logo.png) 4 | 5 | ## Install: 6 | ``` 7 | [Docker](https://www.docker.com/) 8 | ``` 9 | ``` 10 | [Node v6.X](https://nodejs.org/en/) 11 | ``` 12 | ``` 13 | [Npm](https://docs.npmjs.com/getting-started/installing-node) 14 | ``` 15 | ``` 16 | $ git clone https://github.com/Ortee/neo4j-docker-express-api.git neo4j-docker-express 17 | $ cd neo4j-docker-express 18 | $ npm install 19 | ``` 20 | ## Usage 21 | START 22 | ``` 23 | $ docker-compose up 24 | Wait few seconds 25 | Open browser: http://localhost:7474/browser/ 26 | Set neo4j password 27 | Open docker-compose.yml set CONNECTION_STRING_DEV with your new neo4j password 28 | Restart containers (ctrl+c & $ docker-compose up) 29 | ``` 30 | 31 | ## Usage 32 | RUN TESTS 33 | ``` 34 | npm test 35 | ``` 36 | 37 | ## DOCS 38 | ``` 39 | [CYPHER QUERIES](https://neo4j.com/docs/developer-manual/current/cypher/) 40 | ``` 41 | ![alt tag](http://tech.orteedev.pl/graph.png) 42 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | neo4j: 2 | image: neo4j:3.0 3 | ports: 4 | - "7474:7474" 5 | volumes: 6 | - ./db/dbms:/data/dbms 7 | server: 8 | image: node:7 9 | working_dir: /app 10 | command: npm start 11 | volumes: 12 | - .:/app 13 | ports: 14 | - "3000:3000" 15 | links: 16 | - neo4j 17 | environment: 18 | CONNECTION_STRING_DEV: http://neo4j:YourPassword@neo4j:7474 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-express-neo4j", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon server/index.js", 8 | "test": "./node_modules/.bin/jasmine-node spec" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.15.2", 14 | "express": "^4.14.0", 15 | "jasmine-node": "^1.14.5", 16 | "neo4j": "^2.0.0-RC2", 17 | "node-neo4j": "^2.0.3", 18 | "nodemon": "^1.11.0", 19 | "request": "^2.79.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/.eslintrc: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | es6: true 4 | 5 | parserOptions: 6 | ecmaVersion: 2016 7 | 8 | rules: 9 | # Possible Errors 10 | # http://eslint.org/docs/rules/#possible-errors 11 | comma-dangle: [2, only-multiline] 12 | no-control-regex: 2 13 | no-console: 1 14 | no-debugger: 2 15 | no-dupe-args: 2 16 | no-dupe-keys: 2 17 | no-duplicate-case: 2 18 | no-empty-character-class: 2 19 | no-ex-assign: 2 20 | no-extra-boolean-cast: 2 21 | no-extra-parens: [2, functions] 22 | no-extra-semi: 2 23 | no-func-assign: 2 24 | no-invalid-regexp: 2 25 | no-irregular-whitespace: 2 26 | no-obj-calls: 2 27 | no-proto: 2 28 | no-template-curly-in-string: 2 29 | no-unexpected-multiline: 2 30 | no-unreachable: 2 31 | no-unsafe-negation: 2 32 | use-isnan: 2 33 | valid-typeof: 2 34 | 35 | # Best Practices 36 | # http://eslint.org/docs/rules/#best-practices 37 | dot-location: [2, property] 38 | no-fallthrough: 2 39 | no-global-assign: 2 40 | no-multi-spaces: 2 41 | no-octal: 2 42 | no-redeclare: 2 43 | no-self-assign: 2 44 | no-unused-labels: 2 45 | no-useless-call: 2 46 | no-useless-escape: 2 47 | no-void: 2 48 | no-with: 2 49 | 50 | # Strict Mode 51 | # http://eslint.org/docs/rules/#strict-mode 52 | strict: [2, global] 53 | 54 | # Variables 55 | # http://eslint.org/docs/rules/#variables 56 | no-delete-var: 2 57 | no-undef: 2 58 | no-unused-vars: [2, {args: none}] 59 | 60 | # Node.js and CommonJS 61 | # http://eslint.org/docs/rules/#nodejs-and-commonjs 62 | no-mixed-requires: 2 63 | no-new-require: 2 64 | no-path-concat: 2 65 | no-restricted-modules: [2, sys, _linklist] 66 | no-restricted-properties: [2, { 67 | object: assert, 68 | property: deepEqual, 69 | message: Please use assert.deepStrictEqual(). 70 | }, { 71 | property: __defineGetter__, 72 | message: __defineGetter__ is deprecated. 73 | }, { 74 | property: __defineSetter__, 75 | message: __defineSetter__ is deprecated. 76 | }] 77 | 78 | # Stylistic Issues 79 | # http://eslint.org/docs/rules/#stylistic-issues 80 | brace-style: [2, 1tbs, {allowSingleLine: true}] 81 | comma-spacing: 2 82 | comma-style: 2 83 | computed-property-spacing: 2 84 | eol-last: 2 85 | func-call-spacing: 2 86 | indent: [2, 2, {SwitchCase: 1, MemberExpression: 1}] 87 | key-spacing: [2, {mode: minimum}] 88 | keyword-spacing: 2 89 | linebreak-style: [2, unix] 90 | new-parens: 2 91 | no-mixed-spaces-and-tabs: 2 92 | no-multiple-empty-lines: [2, {max: 2, maxEOF: 0, maxBOF: 0}] 93 | no-tabs: 2 94 | no-trailing-spaces: 2 95 | quotes: [2, single, avoid-escape] 96 | semi: 2 97 | semi-spacing: 2 98 | space-before-blocks: [2, always] 99 | space-before-function-paren: [2, never] 100 | space-in-parens: [2, never] 101 | space-infix-ops: 2 102 | space-unary-ops: 2 103 | 104 | # ECMAScript 6 105 | # http://eslint.org/docs/rules/#ecmascript-6 106 | arrow-parens: [2, always] 107 | arrow-spacing: [2, {before: true, after: true}] 108 | constructor-super: 2 109 | no-class-assign: 2 110 | no-confusing-arrow: 2 111 | no-const-assign: 2 112 | no-dupe-class-members: 2 113 | no-new-symbol: 2 114 | no-this-before-super: 2 115 | prefer-const: [2, {ignoreReadBeforeAssign: true}] 116 | rest-spread-spacing: 2 117 | template-curly-spacing: 2 118 | 119 | # Custom rules in tools/eslint-rules 120 | align-function-arguments: 2 121 | align-multiline-assignment: 2 122 | assert-fail-single-argument: 2 123 | new-with-error: [2, Error, RangeError, TypeError, SyntaxError, ReferenceError] 124 | 125 | # Global scoped method and vars 126 | globals: 127 | COUNTER_HTTP_CLIENT_REQUEST: false 128 | COUNTER_HTTP_CLIENT_RESPONSE: false 129 | COUNTER_HTTP_SERVER_REQUEST: false 130 | COUNTER_HTTP_SERVER_RESPONSE: false 131 | COUNTER_NET_SERVER_CONNECTION: false 132 | COUNTER_NET_SERVER_CONNECTION_CLOSE: false 133 | DTRACE_HTTP_CLIENT_REQUEST: false 134 | DTRACE_HTTP_CLIENT_RESPONSE: false 135 | DTRACE_HTTP_SERVER_REQUEST: false 136 | DTRACE_HTTP_SERVER_RESPONSE: false 137 | DTRACE_NET_SERVER_CONNECTION: false 138 | DTRACE_NET_STREAM_END: false 139 | LTTNG_HTTP_CLIENT_REQUEST: false 140 | LTTNG_HTTP_CLIENT_RESPONSE: false 141 | LTTNG_HTTP_SERVER_REQUEST: false 142 | LTTNG_HTTP_SERVER_RESPONSE: false 143 | LTTNG_NET_SERVER_CONNECTION: false 144 | LTTNG_NET_STREAM_END: false 145 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const app = express(); 5 | const neo4j = require('node-neo4j'); 6 | const bodyParser = require('body-parser'); 7 | const db = new neo4j(process.env.CONNECTION_STRING_DEV); 8 | 9 | app.use(bodyParser.json()); 10 | app.use(bodyParser.urlencoded({extended: true})); 11 | 12 | // [GET] ALL PEOPLE 13 | // http://localhost:3000/api/people 14 | app.get('/api/people', function(req, res) { 15 | res.setHeader('Content-Type', 'application/json'); 16 | db.cypherQuery('MATCH (n:Person) RETURN n', {}, function(err, results) { 17 | if (err) { 18 | res.status(503).send('Check database connection'); 19 | } else { 20 | res.send(results.data); 21 | res.status(200).send(); 22 | } 23 | }); 24 | }); 25 | 26 | // [GET] SINGLE PERSON BY NAME 27 | // http://localhost:3000/api/person/John 28 | app.get('/api/person/:name', function(req, res) { 29 | res.setHeader('Content-Type', 'application/json'); 30 | db.cypherQuery('MATCH (p:Person) WHERE p.name = "' + req.params.name + '" RETURN p', 31 | {}, function(err, results) { 32 | if (err) { 33 | res.status(503).send('Check database connection'); 34 | } else { 35 | if (results.data.length > 1) { 36 | res.send(results.data); 37 | res.status(200).send(); 38 | } else { 39 | res.status(204).send(); 40 | } 41 | 42 | } 43 | }); 44 | }); 45 | 46 | // [POST] ADD PERSON 47 | // // http://localhost:3000/api/person 48 | 49 | // { 50 | // "name": "John", 51 | // "sex": "male" 52 | // } 53 | app.post('/api/person', function(req, res) { 54 | req.accepts('application/json'); 55 | db.insertNode({ 56 | name: req.body.name, 57 | sex: req.body.sex 58 | }, ['Person'], function(err) { 59 | err != true ? 60 | res.status(201).send() : 61 | res.status(404).send(); 62 | }); 63 | }); 64 | 65 | // [POST] ADD RELATIONSHIP BEETWEN PEOPLE 66 | // // http://localhost:3000/api/knows 67 | 68 | // { 69 | // "name1": "John", 70 | // "name2": "Ann" 71 | // } 72 | app.post('/api/know', function(req, res) { 73 | req.accepts('application/json'); 74 | db.cypherQuery('MATCH (a:Person { name: "' + req.body.name1 + '" }), (b:Person { name: "' + req.body.name2 + '" }) CREATE (a)-[:KNOWS]->(b)', 75 | {}, function(err) { 76 | err != true ? 77 | res.status(201).send() : 78 | res.status(404).send(); 79 | }); 80 | }); 81 | 82 | 83 | // [DELETE] DELETE PERSON 84 | // // http://localhost:3000/api/person 85 | 86 | // { 87 | // "name": "John" 88 | // } 89 | app.delete('/api/person', function(req, res) { 90 | req.accepts('application/json'); 91 | db.cypherQuery('MATCH (p:Person)-[rel:KNOWS]->() WHERE p.name = "' + req.body.name + '" DELETE rel', 92 | {}, function(err) { 93 | err != true ? 94 | res.status(204).send() : 95 | res.status(409).send(); 96 | }); 97 | }); 98 | 99 | app.listen(3000, function() { 100 | console.log('Example app listening on port 3000!') 101 | }); 102 | -------------------------------------------------------------------------------- /spec/index.spec.js: -------------------------------------------------------------------------------- 1 | const request = require("request"); 2 | 3 | var url = 'http://localhost:3000'; 4 | 5 | describe("Neo4j simple API by Ortee", function() { 6 | 7 | describe("Server check", function() { 8 | it("GET /api/people", function(done) { 9 | request.get(url + '/api/people', function(error, response, body) { 10 | expect(response.statusCode).toBe(200); 11 | done(); 12 | }); 13 | }); 14 | it("GET /api/person/*", function(done) { 15 | request.get(url + '/api/person/qweasdzxcasdqweasdzxc', function(error, response, body) { 16 | expect(response.statusCode).toBe(204); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | 22 | describe('description', () => { 23 | it("POST /api/person - ADD MALE", function(done){ 24 | request({ 25 | url: url + '/api/person', 26 | method: 'POST', 27 | headers: { 28 | 'Content-Type': 'application/json' 29 | }, 30 | body: JSON.stringify({name: 'Adam123', sex: 'male'}) 31 | }, function(error, response, body){ 32 | if(!error) { 33 | expect(response.statusCode).toBe(201); 34 | done(); 35 | } 36 | }); 37 | }); 38 | 39 | it("POST /api/person - ADD FEMALE", function(done){ 40 | request({ 41 | url: url + '/api/person', 42 | method: 'POST', 43 | headers: { 44 | 'Content-Type': 'application/json' 45 | }, 46 | body: JSON.stringify({name: 'Ann123', sex: 'female'}) 47 | }, function(error, response, body){ 48 | if(!error) { 49 | expect(response.statusCode).toBe(201); 50 | done(); 51 | } 52 | }); 53 | }); 54 | 55 | it("GET /api/person/* GET MALE", function(done) { 56 | request.get(url + '/api/person/Adam123', function(error, response, body) { 57 | expect(response.statusCode).toBe(200); 58 | done(); 59 | }); 60 | }); 61 | 62 | it("GET /api/person/* GET FEMALE", function(done) { 63 | request.get(url + '/api/person/Ann123', function(error, response, body) { 64 | expect(response.statusCode).toBe(200); 65 | done(); 66 | }); 67 | }); 68 | 69 | it("POST /api/know ADD RELATIONSHIP", function(done){ 70 | request({ 71 | url: url + '/api/know', 72 | method: 'POST', 73 | headers: { 74 | 'Content-Type': 'application/json' 75 | }, 76 | body: JSON.stringify({name1: 'Ann123', name2: 'Adam123'}) 77 | }, function(error, response, body){ 78 | if(!error) { 79 | expect(response.statusCode).toBe(201); 80 | done(); 81 | } 82 | }); 83 | }); 84 | 85 | it("DELETE /api/person DELETE MALE", function(done){ 86 | request({ 87 | url: url + '/api/person', 88 | method: 'DELETE', 89 | headers: { 90 | 'Content-Type': 'application/json' 91 | }, 92 | body: JSON.stringify({name: 'Adam123'}) 93 | }, function(error, response, body){ 94 | if(!error) { 95 | expect(response.statusCode).toBe(204); 96 | done(); 97 | } 98 | }); 99 | }); 100 | 101 | it("DELETE /api/person DELETE FEMALE", function(done){ 102 | request({ 103 | url: url + '/api/person', 104 | method: 'DELETE', 105 | headers: { 106 | 'Content-Type': 'application/json' 107 | }, 108 | body: JSON.stringify({name: 'Ann123'}) 109 | }, function(error, response, body){ 110 | if(!error) { 111 | expect(response.statusCode).toBe(204); 112 | done(); 113 | } 114 | }); 115 | }); 116 | 117 | }); 118 | }); 119 | --------------------------------------------------------------------------------