MySQL Editor in NodeJS ~ Part one
Tags: mysql, node, learning, opensource
Published: 2018-09-10 15:25:45

---
title: MySQL Editor in NodeJS ~ Part one
published: true
description: Learning to use NodeJS
tags: MySQL, node, learning, opensource
cover_image: https://thepracticaldev.s3.amazonaws.com/i/iply2eckh6x45bbw03ml.png
series: Project NodeTree
---

# MySQL Editor in NodeJS

This is a short break from my [Adventures of a Hobbyist](https://dev.to/link2twenty/adventures-of-a-hobbyist--index-4oj7) series, where I'm learning node, to put some of what I've learnt into practice.

I'm not sure how long this mini-series will last but the aim is to make a web interface that lets you connect to a MySQL database to view and, if I get around to it, edit the tables.

Once I have a working prototype I'll put it up on GitHub and we'll see if anyone has any pull requests or issues to report.

## Progress so far

So far I've put in the bare bones of the connection screen, this is loosely modelled on MySQL workbench but with a material twist.

Home screen:

![Home](https://thepracticaldev.s3.amazonaws.com/i/3qxra0bfvz6w4yhbgdy4.png)

Dialogue concept:

![Dialogue](https://thepracticaldev.s3.amazonaws.com/i/mzczuhskuunzdpvk0njk.png)

I use a JSON file to store the server details, not passwords, of course, to do this I'm using `confHelper` which is something I wrote earlier, here's the [post](https://dev.to/link2twenty/adventures-of-a-hobbyist--part-five-532c) where I look at it. I'm also using `expressjs` for my routing this time.

## The code

I haven't really commented the code so far and I'm worried it will get out of hand fast. If you have any tips for commenting the code nicely I'm all ears.

Just to give you an idea of my super simple structure here's a picture.

![stucture](https://thepracticaldev.s3.amazonaws.com/i/hsejjisnyhwe3a4b3ifp.png)

As with all things with my projects if I'm doing something wrong let me know 😀


**Server:**

```javascript
const express = require('express');
const app = express();
var path = require('path');
const ConfHelper = require('./conf_import');
const ch = new ConfHelper('servers');

app.get('/', function(req, res) {
  res.sendFile(path.join(__dirname + '/pages/index.html'));
});

app.get('/styles/default.css', function(req, res) {
  res.sendFile(path.join(__dirname + '/styles/default.css'));
});

app.get('/favicon.ico', function(req, res) {
  res.sendFile(path.join(__dirname + '/pages/favicon.ico'));
});

app.get('/api/servers', function(req, res) {
  res.json(ch.config.get().servers);
});

app.listen(80);
```

**Homepage:**

```html
<head>
  <title>nodetree</title>
  <link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
  <link rel="stylesheet" href="/styles/default.css">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>

<body>
  <header><span class="blue">node</span><span class="orange">tree</span></header>
  <nav>MySQL connections</nav>
  <div class="fab shadow shadow-hover"> + </div>
  <div class="box-container"></div>
  <div hidden class="modal">
    <div class="backdrop"></div>
    <div class="content shadow">
      <div class="header">Add new connection</div>
      <div class="central"></div>
      <div class="footer"><button class="modal-close">Cancel</button><button>Add</button></div>
    </div>
  </div>
</body>

<script>
  const boxTemplate = (name, host, user) => {
    // Main node
    let node = document.createElement('div');
    node.classList.add('boxes');
    node.classList.add('shadow');
    node.classList.add('shadow-hover');
    // Div containing name
    let nameNode = document.createElement('div');
    nameNode.classList.add('name');
    nameNode.appendChild(document.createTextNode(name));
    node.appendChild(nameNode);
    // Span containing host
    let hostNode = document.createElement('span');
    hostNode.appendChild(document.createTextNode(host));
    node.appendChild(hostNode);
    // Add break
    let breakNode = document.createElement('br');
    node.appendChild(breakNode);
    // Span contain user
    let userNode = document.createElement('span');
    userNode.appendChild(document.createTextNode(`${user}@${host}`));
    node.appendChild(userNode);

    node.addEventListener('click', event => {
      console.log(event.target.querySelector('.name').innerText);
    })

    return node
  };

  const closeModal = () => {
    let modal = document.querySelector('.modal');
    modal.hasAttribute('hidden') || modal.setAttribute('hidden','');
  }

  const fab = document.querySelector('.fab');
  fab.addEventListener('click', event => {
    let modal = document.querySelector('.modal');
    modal.hasAttribute('hidden') && modal.removeAttribute('hidden');
  })

  const modalClose = document.querySelector('.modal-close');
  modalClose.addEventListener('click', closeModal)

  const modalBack = document.querySelector('.modal .backdrop');
  modalBack.addEventListener('click', closeModal)

  let oReq = new XMLHttpRequest();
  oReq.addEventListener("load", _setBoxes);
  oReq.open("GET", "/api/servers");
  oReq.send()

  function _setBoxes() {
    const boxContainer = document.querySelector('.box-container');
    const res = JSON.parse(this.response);
    let servers = Object.keys(res);

    for (let server of servers) {
      boxContainer.appendChild(boxTemplate(server, res[server].host, res[server].user));
    }
  }
</script>

```

**Styles:**

```css
@import url('https://fonts.googleapis.com/css?family=Open+Sans');

    :root {
      --header-font-size: 12.5vh;
      --header-height: 25vh;
      --nav-height: 60px;
      --fab-size: var(--nav-height);
      --box-flex: flex-start;
      --modal-size: 80;
      --modal-radius: 6px;
    }

    @media only screen and (min-device-width : 768px) and (max-device-width : 1024px) {
      :root {
        --header-font-size: 60px;
        --header-height: 15vh;
        --nav-height: 60px;
        --box-flex: flex-start;
        --modal-size: 80;
        --modal-radius: 6px;
      }
    }

    @media only screen and (max-device-width : 768px) {
      :root {
        --header-font-size: 60px;
        --header-height: 15vh;
        --nav-height: 50px;
        --box-flex: space-around;
        --modal-size: 100;
        --modal-radius: 0px;
      }
    }

    body {
      background: #e2e1e0;
      font-family: 'Open Sans';
      margin: 0;
    }

    header {
      height: var(--header-height);
      width: 100vw;
      background: white;
      text-align: center;
      line-height: var(--header-height);
      font-size: var(--header-font-size);
    }

    .blue {
      color: #303F9F;
    }

    .orange {
      color: #FF5722;
    }

    nav {
      padding: 0px 25px;
      background: #3F51B5;
      height: var(--nav-height);
      line-height: var(--nav-height);
      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
      color: #fff;
    }

    nav button.add {
      border-radius: 12px;
      background: rgba(0, 0, 0, 0);
      height: 24px;
      width: 24px;
      box-sizing: border-box;
      color: #fff;
      border: #fff solid 1px;
    }

    .fab {
      position: relative;
      cursor: pointer;
      text-align: center;
      background-color: #FF5722;
      color: white;
      /* calculated from --fab-size */
      top: calc((var(--fab-size) / 2) * -1);
      width: var(--fab-size);
      height: var(--fab-size);
      margin-bottom: calc(var(--fab-size) * -1);
      margin-left: calc(100vw - var(--fab-size) - 25px);
      border-radius: calc(var(--fab-size) / 2);
      font-size: calc((var(--fab-size) / 8) * 5);
      line-height: var(--fab-size);
    }

    .shadow {
      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
      transition: box-shadow 0.3s cubic-bezier(.25, .8, .25, 1);
    }

    .shadow-hover:hover {
      box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
    }

    .box-container {
      display: flex;
      justify-content: var(--box-flex);
      flex-wrap: wrap;
      padding: 25px;
    }

    .boxes {
      cursor: pointer;
      min-width: 225px;
      height: 90px;
      border-radius: 2px;
      background: #fff;
      color: #757575;
      padding: 0 0 10px 10px;
      margin: 10px;
    }

    .boxes .name {
      padding: 5px 0 10px 0;
      font-size: 1.5em;
      color: #212121;
      pointer-events: none;
    }

    .boxes span {
      pointer-events: none;
    }

    .modal {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
    }

    .modal .backdrop {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      background: rgba(46,46,46,0.6);
    }

    .modal .content {
      position: fixed;
      top: calc((100vh - (var(--modal-size) * 1vh))/2);
      left: calc((100vw - (var(--modal-size) * 1vw))/2);
      width: calc((var(--modal-size) * 1vw));
      height: calc((var(--modal-size) * 1vh));
      border-radius: var(--modal-radius);
      background: #fff;
      color: #757575;
    }

    .modal .content .header {
      height: 50px;
      line-height: 50px;
      color: #212121;
      border-bottom: 1px solid #BDBDBD;
      font-size: 34px;
      text-align: center;
    }

    .modal .content .central {
      height: calc((var(--modal-size) * 1vh) - 102px);
      color: #C5CAE9;
    }

    .modal .content .footer {
      height: 50px;
      line-height: 50px;
      color: #303F9F;
      border-top: 1px solid #BDBDBD;
      padding: 0 23px;
      text-align: right;
    }

    .modal .content .footer button {
      cursor: pointer;
      color: #303F9F;
      border: none;
      background: none;
      height: 50px;
      font-size: 18px;
    }

```

## The end

So that's as far as I got today, I have a long to-do list you may have noticed this version doesn't even interact with any MySQL stuff yet. It's a long journey, thank you for coming along with me.

Thanks again 🦄🦄🦄