diff --git a/.gitignore b/.gitignore
index d074aac329494a87b34f005d21d280a96b42c844..4a4d573d925083d5124d2effb6d876166b82230a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,8 +41,8 @@ bower_components
 build/Release
 
 # Dependency directories
-node_modules/
-jspm_packages/
+**node_modules/
+**jspm_packages/
 
 # Typescript v1 declaration files
 typings/
diff --git a/app.js b/login/app.js
similarity index 69%
rename from app.js
rename to login/app.js
index e0813426995fafaffcd91c9e4de8e2fb563dafa0..d187f73a03641759af0be1d7b6e8b55e6c080f2e 100644
--- a/app.js
+++ b/login/app.js
@@ -17,14 +17,4 @@ app.use(express.static(path.join(__dirname, 'public')));
 app.use('/', indexRouter);
 app.use('/users', usersRouter);
 
-app.get("/user/:userid/", function (req, res) {
-  res.send({test: 'data'});
-});
-
-app.get("/:str/blah/:name/:id", function (req, res) {
-  res.send("Get request to " + req.url + " was received!");
-  res.send(req.params);
-});
-
-
 module.exports = app;
diff --git a/login/bin/www b/login/bin/www
new file mode 100755
index 0000000000000000000000000000000000000000..402db4725f804680db31c126543a552be9b9206a
--- /dev/null
+++ b/login/bin/www
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('login:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+  var port = parseInt(val, 10);
+
+  if (isNaN(port)) {
+    // named pipe
+    return val;
+  }
+
+  if (port >= 0) {
+    // port number
+    return port;
+  }
+
+  return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+  if (error.syscall !== 'listen') {
+    throw error;
+  }
+
+  var bind = typeof port === 'string'
+    ? 'Pipe ' + port
+    : 'Port ' + port;
+
+  // handle specific listen errors with friendly messages
+  switch (error.code) {
+    case 'EACCES':
+      console.error(bind + ' requires elevated privileges');
+      process.exit(1);
+      break;
+    case 'EADDRINUSE':
+      console.error(bind + ' is already in use');
+      process.exit(1);
+      break;
+    default:
+      throw error;
+  }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+  var addr = server.address();
+  var bind = typeof addr === 'string'
+    ? 'pipe ' + addr
+    : 'port ' + addr.port;
+  debug('Listening on ' + bind);
+}
diff --git a/login/package.json b/login/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..b68cb9c85d5ab775ffd7a9e5942f7fde4b0e052e
--- /dev/null
+++ b/login/package.json
@@ -0,0 +1,14 @@
+{
+  "name": "login",
+  "version": "0.0.0",
+  "private": true,
+  "scripts": {
+    "start": "node ./bin/www"
+  },
+  "dependencies": {
+    "cookie-parser": "~1.4.3",
+    "debug": "~2.6.9",
+    "express": "~4.16.0",
+    "morgan": "~1.9.0"
+  }
+}
diff --git a/login/public/index.html b/login/public/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..ab1ad8a94fe2f508b199d111855f322c1fb20bee
--- /dev/null
+++ b/login/public/index.html
@@ -0,0 +1,13 @@
+<html>
+
+<head>
+  <title>Express</title>
+  <link rel="stylesheet" href="/stylesheets/style.css">
+</head>
+
+<body>
+  <h1>Express</h1>
+  <p>Welcome to Express</p>
+</body>
+
+</html>
diff --git a/login/public/stylesheets/style.css b/login/public/stylesheets/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..9453385b9916ce9bc5e88d2f5d8cd8a554223590
--- /dev/null
+++ b/login/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+  padding: 50px;
+  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+  color: #00B7FF;
+}
diff --git a/routes/index.js b/login/routes/index.js
similarity index 100%
rename from routes/index.js
rename to login/routes/index.js
diff --git a/routes/users.js b/login/routes/users.js
similarity index 64%
rename from routes/users.js
rename to login/routes/users.js
index 93721a074d853c1891cd6f1b40bf78bb90b2cfa8..623e4302bee32ccc080d1c83ee2e55a426c9bac8 100644
--- a/routes/users.js
+++ b/login/routes/users.js
@@ -6,8 +6,4 @@ router.get('/', function(req, res, next) {
   res.send('respond with a resource');
 });
 
-router.get('/:id', function(req, res, next) {
-  res.send("information about user with ID " + req.params.id);
-});
-
 module.exports = router;
diff --git a/misc/import_memberdb.js b/misc/import_memberdb.js
index 33de5d96b2057adc19b1011de4e70b171be5d602..3701edc6cd8d34938e83723fd178e66848032747 100755
--- a/misc/import_memberdb.js
+++ b/misc/import_memberdb.js
@@ -8,7 +8,7 @@ const conf = require('./pg.json');
 const Member = require('../models/memberSchema');
 const TLA = require('../models/tlaSchema');
 const Fuse = require('fuse.js');
-const tlaParse = require('./tlaParser');
+const tlaParser = require('./tlaParser');
 
 /* DEPRECATED: specify stuff via environment variables
   PGUSER=uccmemberdb PGHOST=localhost PGPASSWORD=[redacted] PGDATABASE=uccmemberdb_2018 PGPORT=5432 node misc/import_memberdb.js
@@ -54,7 +54,9 @@ db.once('open', function() {
 });
 
 const tlafile = process.argv[2];
-var tlas = tlaParse(tlafile);
+var tlas = tlaParser.parse(tlafile);
+tlaParser.print(tlas);
+console.log('Finished parsing TLAs, got ' + tlas.length + ' different TLAs.');
 
 /** old memberdb schema from postgresql:
 CREATE TABLE memberdb_member (
@@ -99,9 +101,6 @@ function processOldMembers(err, res) {
 };
 
 function matchTLAs(members) {
-  // clone original TLA array so we can remove TLAs we find and end up with a list of unclaimed TLAs.
-  var tlasUnused = tlas.slice(0);
-
   // Fuse doesn't like to search across multiple fields so we have to combine firstname and lastname again to make it work.
   var fixedTLAs = [];
   for (var i = 0; i < tlas.length; i++) {
@@ -124,26 +123,61 @@ function matchTLAs(members) {
   };
   var fuse = new Fuse(fixedTLAs, options); // "list" is the item array
 
+  var saved = 0;
+  var toSave = 0;
+
+  function tryCloseDB() {
+    saved += 0.5;
+    if (saved == toSave) {
+      // everything is saved, time to stop.
+      db.close(function (err) {
+        if (err) {
+          console.warn(err);
+          console.warn("could not close mongodb connection!");
+        } else {
+          console.log("closed mongodb connection.");
+        }
+      });
+    }
+  }
+
   // now loop through members and find their TLAs
   for (var mi = 0; mi < members.length; mi++) {
     var m = members[mi];
+
     var results = fuse.search(m.firstname + " " + m.lastname);
     // console.log("searching for tlas for " + m.firstname + " " + m.lastname);
     if (results.length > 0) {
       console.log(m.firstname + " has " + results.length + " tlas, using [" + results[0].tla + "]");
       // Some false positives: only use the first TLA.
       m.tlas = [results[0].tla];
-      var usedId = tlasUnused.indexOf(results[0].tla);
-      tlasUnused.splice(usedId, usedId + 1);
-      m.save();
+
+      // Update Member to include TLAs we found
+      toSave++;
+      m.save(function (err) {
+        // since the saving is done asynchronously, we need to close the db after everything has been saved.
+        if (err) {
+          console.warn("error saving TLA to member!");
+          console.warn(err);
+        }
+        tryCloseDB();
+      });
+
+      // query and update TLA document so we can associate it with a member.
+      TLA.findOneAndUpdate({tla: results[0].tla}, {_memberId: m._id}, function (err, doc) {
+        if (err) {
+          console.warn(err);
+        }
+        console.warn("Updated TLA [" + doc.tla + "] with member ID " + doc._memberId);
+        tryCloseDB();
+      });
     }
     if (results.length == 0) {
       // console.log(m.firstname + " has no TLA.");
     }
   }
 
-  console.log(tlas.length - tlasUnused.length + " TLAs assigned to members, total " + tlas.length + ", unclaimed " + (tlasUnused.length));
-  return members;
+  console.log(toSave + " TLAs assigned to members, total " + tlas.length + ", unclaimed " + (tlas.length - toSave));
 }
 
 function convertMember(row) {
@@ -183,5 +217,4 @@ function convertMember(row) {
 function verifyMongoMembers(err, res) {
   console.log("Currently " + res.length + " members in mongodb.");
   matchTLAs(res);
-  db.close();
 }
\ No newline at end of file
diff --git a/misc/tlaParser.js b/misc/tlaParser.js
index e923f00589c654b584e8fcdf51528949b4cc317a..195d00de8d9769ce06c9c54a853a66cee88993c1 100644
--- a/misc/tlaParser.js
+++ b/misc/tlaParser.js
@@ -1,6 +1,6 @@
 const fs = require('fs');
 
-module.exports = function parseTLAs(tlafile) {
+module.exports.parse = function parseTLAs(tlafile) {
   console.log("Loading TLAs from '" + tlafile + "'...");
   var tlas = [];
   try {
@@ -40,15 +40,18 @@ module.exports = function parseTLAs(tlafile) {
       };
       tlas.push(tla);
       console.log(tla.firstname + " / " + tla.lastname + " / " + tla.tla);
-      // process.stdout.write("[" + s[0] + "] " + ((tlas.length % 16 == 0) ? "\n" : ""));
     }
-    // process.stdout.write("\n");
   } catch (err) {
     console.warn("Error parsing TLA data!");
     console.warn(err);
     process.exit(1);
   }
-
-  console.log('Finished parsing TLAs, got ' + tlas.length + ' different TLAs.');
   return tlas;
-}
\ No newline at end of file
+};
+
+module.exports.print = function(tlas) {
+  for (var i = 0; i < tlas.length; i++) {
+    process.stdout.write("[" + tlas[i].tla + "] " + ((i % 16 == 15) ? "\n" : ""));
+  }
+  process.stdout.write("\n");
+};
\ No newline at end of file
diff --git a/user/app.js b/user/app.js
new file mode 100644
index 0000000000000000000000000000000000000000..9d4d489523eb979df6eaa993c92a3f7e0db4be3f
--- /dev/null
+++ b/user/app.js
@@ -0,0 +1,20 @@
+var express = require('express');
+var path = require('path');
+var cookieParser = require('cookie-parser');
+var logger = require('morgan');
+
+var indexRouter = require('./routes/index');
+var usersRouter = require('./routes/users');
+
+var app = express();
+
+app.use(logger('dev'));
+app.use(express.json());
+app.use(express.urlencoded({ extended: false }));
+app.use(cookieParser());
+//app.use(express.static(path.join(__dirname, 'public')));
+
+app.use(indexRouter);
+app.use('/users', usersRouter);
+
+module.exports = app;
diff --git a/bin/www b/user/bin/www
similarity index 93%
rename from bin/www
rename to user/bin/www
index 130bb51e643ecbee01874cca037ddd2a6570f425..5c91add6d055e49338f740fe0e1e5c7467864970 100755
--- a/bin/www
+++ b/user/bin/www
@@ -21,7 +21,11 @@ server.on('listening', onHTTPListening);
 /** Connect to mongodb */
 mongoose.connect('mongodb://localhost/uccportal-dev'); 
 var db = mongoose.connection;
-db.on('error', console.error.bind(console, 'db connection error:'));
+db.on('error', function (err) {
+  console.warn(err);
+  console.warn("Error connecting to DB!");
+  process.exit(1);
+});
 db.once('open', function() {
   console.log("Connected to db");
 });
diff --git a/models/memberSchema.js b/user/models/memberSchema.js
similarity index 73%
rename from models/memberSchema.js
rename to user/models/memberSchema.js
index e00c595972894d57020c34671f02b29e6ad5c6de..e37996180812ca001e582c8924ff70e47a760a46 100644
--- a/models/memberSchema.js
+++ b/user/models/memberSchema.js
@@ -1,6 +1,11 @@
 'use strict';
 const mongoose = require('mongoose');
 
+var renewalSchema = mongoose.Schema({
+  renewtype: {type: String },
+  date: { type: Date },
+})
+
 // Here we define the schema used by the model for gumby documents
 var memberSchema = mongoose.Schema({
   firstname: String,
@@ -12,9 +17,9 @@ var memberSchema = mongoose.Schema({
   phone: String,
   birthdate: Date,
   username: String,
-  tlas: [String],
+  tlas: [ { type: String } ],
   signupdate: Date,
-  renewals: [{renewtype: String, date: Date}]
+  renewals: [ { type: renewalSchema } ]
 });
 
 // And we export (return) a model based on the schema
diff --git a/models/tlaSchema.js b/user/models/tlaSchema.js
similarity index 78%
rename from models/tlaSchema.js
rename to user/models/tlaSchema.js
index 8770569d4771fbf463646d04dd0f4c21c347cea4..145560a9050d487632f956fc828775af27bc238f 100644
--- a/models/tlaSchema.js
+++ b/user/models/tlaSchema.js
@@ -4,6 +4,7 @@ var tlaSchema = mongoose.Schema({
   tla: String, // Three Letter Acronym
   firstname: String, // Name associated with TLA (even if a member, yes I know it's duplicated but TLAs are weird.)
   lastname: String,
+  _memberId: mongoose.Schema.Types.ObjectId, // ID of corresponding member if found
 });
 
 module.exports = mongoose.model('TLA', tlaSchema);
\ No newline at end of file
diff --git a/package-lock.json b/user/package-lock.json
similarity index 99%
rename from package-lock.json
rename to user/package-lock.json
index 40b029c7e2bc809b0edf2158b51bf54c790129a7..025b34bc2f80f49c60f6963e548f42f3d1a6755e 100644
--- a/package-lock.json
+++ b/user/package-lock.json
@@ -1391,6 +1391,12 @@
         }
       }
     },
+    "fuse.js": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.2.0.tgz",
+      "integrity": "sha1-8ESOgGmFW/Kj5oPNwdMg5+KgfvQ=",
+      "dev": true
+    },
     "get-stream": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
diff --git a/package.json b/user/package.json
similarity index 96%
rename from package.json
rename to user/package.json
index 34a734671403c41fc0c5f7fabc40c38602b46e91..697af16560a57aed1e79490344e24f8a78026345 100644
--- a/package.json
+++ b/user/package.json
@@ -14,6 +14,7 @@
   "author": "frekk",
   "license": "GPL-3.0-or-later",
   "devDependencies": {
+    "fuse.js": "^3.2.0",
     "nodemon": "^1.17.5",
     "pg": "^7.4.3"
   },
diff --git a/user/routes/index.js b/user/routes/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..09d3f4ed664e8653f4381534e368aa4c393dca85
--- /dev/null
+++ b/user/routes/index.js
@@ -0,0 +1,9 @@
+var express = require('express');
+var router = express.Router();
+
+/* GET home page. */
+router.get('/', function(req, res, next) {
+  res.send("Welcome to UCCPortal REST API.");
+});
+
+module.exports = router;
diff --git a/user/routes/tlas.js b/user/routes/tlas.js
new file mode 100644
index 0000000000000000000000000000000000000000..13daddb86e0cb2d3006d488d0e4e11fddc7693df
--- /dev/null
+++ b/user/routes/tlas.js
@@ -0,0 +1,9 @@
+var express = require('express');
+var router = express.Router();
+var TLA = require('../models/tlaSchema');
+
+router.get('/:tla', function (req, res, next) {
+
+});
+
+model.exports = router;
\ No newline at end of file
diff --git a/user/routes/users.js b/user/routes/users.js
new file mode 100644
index 0000000000000000000000000000000000000000..3ee4031e32a7ba599bb276cf62976cd081c6b807
--- /dev/null
+++ b/user/routes/users.js
@@ -0,0 +1,30 @@
+var express = require('express');
+var router = express.Router();
+var Member = require('../models/memberSchema');
+
+/* GET users listing. */
+router.get('/', function(req, res, next) {
+    Member.find({}).select('username -_id').exec(function (err, members) {
+      var r = [];
+      for (var i = 0; i < members.length; i++) {
+        if (members[i].username) {
+          r.push(members[i].username);
+        }
+      }
+      res.json(r);
+    });
+});
+
+router.get('/:username', function(req, res, next) {
+  Member.find({username: req.params.username}).select('-_id').exec(function (err, d) {
+    res.json(d);
+  });
+});
+
+router.get('/:username/tlas', function (req, res, next) {
+  Member.find({username: req.params.username}).select('tlas -_id').exec(function (err, d) {
+    res.json(d);
+  })
+});
+
+module.exports = router;