DEV Community

Cover image for MySQL Editor in NodeJS ~ Part one
Andrew Bone
Andrew Bone

Posted on • Updated on

MySQL Editor in NodeJS ~ Part one

MySQL Editor in NodeJS

This is a short break from my Adventures of a Hobbyist 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

Dialogue concept:

Dialogue

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 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

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

Server:

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);
Enter fullscreen mode Exit fullscreen mode

Homepage:

<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>

Enter fullscreen mode Exit fullscreen mode

Styles:

@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;
    }

Enter fullscreen mode Exit fullscreen mode

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 🦄🦄🦄

Top comments (0)