Categories
Express Nodejs Vue 3

Create a Todo App with Vue 3, Express and Sematext

Spread the love

Vue 3 is the up and coming version of the popular Vue front end framework.

We can pair that we the back end of our choice to create an app that we want to create.

In this article, we’ll create a Vue 3 front end that’s paired with an Express back end that uses Sematext for logging.

Get Started

We can start by creating our scaffold.

First, we create a project folder with a backend and frontend folders inside.

Then we can go into our backend folder and create our Express app.

We can use the Express generator package to make this easy.

To run it, we run:

npx express-generator

in our backend folder to add the files.

Then we run:

npm i

to install the packages.

Next, we create our Vue 3 front end project.

To do that, we go into the project folder root and run:

npm init vite-app frontend

This will create the project files in the frontend folder and install the required packages.

Backend

Now we have the scaffolding for both apps, we can work on the back end app.

We install a few more packages that we’ll need for our back end.

To do that we run:

npm i cors dotenv sematext-agent-express sqlite3

cors is a package to let us communicate between front end and back end regardless of the domain they’re in.

dotenv lets us read the environment variables.

sematext-agent-express is the Sematext package for Express apps.

sqlite3 lets us save data to a SQLite database.

Next, we create a todo.js file in the routes folder.

And then in app.js, we change the existing code to:

require('dotenv').config()
const { stHttpLoggerMiddleware } = require('sematext-agent-express')
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var cors = require('cors');

var indexRouter = require('./routes/index');
var todosRouter = require('./routes/todo');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(cors())

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(stHttpLoggerMiddleware)

app.use('/', indexRouter);
app.use('/todos', todosRouter);



// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

We added the todosRouter middleware to let us use the todos route file.

And we have:

app.use('/todos', todosRouter);

to use the todosRouter file.

Also, we have:

require('dotenv').config()

to read the .env file which we’ll need for reading the API token for Sematext.

We add the Sematext Express middleware into our app with:

app.use(stHttpLoggerMiddleware)

so we can use its logging capabilities in our app.

We also have:

var cors = require('cors');

to let us do cross-domain communication.

Next, we go to todo.js and replace the existing code with:

var express = require('express');
var sqlite3 = require('sqlite3').verbose();
const { stLogger } = require('sematext-agent-express')

var db = new sqlite3.Database('./todos.db');
var router = express.Router();

router.get('/', (req, res) => {
  db.serialize(() => {
    db.all("SELECT * FROM todos", (err, results) => {
      if (err) {
        stLogger.error(err);
        return res.status(500).send(err);
      }
      stLogger.info(results);
      res.json(results);
    });
  });
});

router.post('/', (req, res) => {
  const { name } = req.body;

  db.serialize(() => {
    db.run("INSERT INTO todos (name) VALUES (?)", name, (err, results) => {
      if (err) {
        stLogger.error(err);
        return res.status(500).send(err);
      }
      stLogger.info(results);
      res.json(results);
    });
  });
});

router.put('/:id', (req, res) => {
  const { id } = req.params;
  const { name } = req.body;

  db.serialize(() => {
    db.run("UPDATE todos SET name = ? WHERE id = ?", name, id, (err, result) => {
      if (err) {
        stLogger.error(err);
        return res.status(500).send(err);
      }
      stLogger.info(result);
      res.json(result);
    });
  });
});

router.delete('/:id', (req, res) => {
  const { id } = req.params;
  db.serialize(() => {
    db.run("DELETE FROM todos WHERE id = ?", id, (err, results) => {
      if (err) {
        stLogger.error(err);
        return res.status(500).send(err);
      }
      stLogger.info(results);
      res.json(results);
    });
  });
});

module.exports = router;

We add the routes with the router methods.

We use the stLogger object from the sematext-agent-express package to let us do the logging.

In each route, we have the stLogger.error method to log errors.

And we have stLogger.info to log other information like the database results.

Each time the route middleware runs, we’ll see something logged.

If there’s an error we return that as the response back to the client with the if statements.

We call db.serialize to run database queries in sequence.

And we use db.run to run INSERT, UPDATE, DELETE statements.

To run SELECT queries, we run db.all to get all the rows.

We use parameterized queries so that values are escaped before we run the queries.

req.params gets the request parameters from the URL.

req.body gets the request body.

We have:

var db = new sqlite3.Database('./todos.db');

The todos.db file will be created if it doesn’t exist.

Once it’s created we can open the file by using the SQLite browser from https://sqlitebrowser.org/.

We can download the file and install it.

Then we can open the todos.db file and run:

CREATE TABLE todos (id INTEGER PRIMARY KEY, name TEXT NOT NULL)

to create the todos table so we can write to it.

Now the SQL statements in our code should run properly.

Then we go into the backend folder and create the .env file.

And then we add the LOGS_TOKEN key so that we can use Sematext for logging:

LOGS_TOKEN=YOUR_OWN_KEY_FROM_SEMATEXT

We can get the key by signing up for an account by going to https://apps.sematext.com/ui/login/.

Once we’re in, we can click on Apps on the left menu.

Then click on New App on the top right to create our app.

Once you see the app entry, we click on the menu button on the right side of the row, and click Integrations.

Then we see Node.js on the left side of what’s open and follow the instructions from there.

Front End

Now that the back end is done, we can move onto the front end.

First, we install the axios HTTP client so that we can make HTTP requests.

We can do that by running:

npm i axios

We go to the frontend/components folder and create a Todo.vue file.

Then we add:

<template>
  <form @submit.prevent="save">
    <input type="text" v-model="todo.name" />
    <input type="submit" value="save" />
    <button type="button" @click="deleteTodo" v-if='todo.id'>delete</button>
  </form>
</template>

<script>
import axios from "axios";
const APIURL = "http://localhost:3000";

export default {
  name: "Todo",
  props: {
    todo: {
      type: Object,
      default() {
        return {};
      },
    },
  },
  data() {
    return {
      name: "",
    };
  },
  methods: {
    async save() {
      const { name } = this.todo;
      if (this.todo.id) {
        await axios.put(`${APIURL}/todos/${this.todo.id}`, { name });
      } else {
        await axios.post(`${APIURL}/todos`, { name });
      }
      this.$emit("saved-todo");
    },

    async deleteTodo() {
      const { data } = await axios.delete(`${APIURL}/todos/${this.todo.id}`);
      this.$emit("saved-todo");
    },
  },
};
</script>

to it.

We use the form to add or edit the files.

Also, we have a delete button to delete the todos.

The save method makes a PUT request to our back end if the todo has an id.

This mneans it’s an existing entry, so we make a PUT request.

Otherwise, we make a POST request.

Once they’re successful, then we emit the save-todos event so that we can get the latest data later.

Also, we have the deleteTodo method so that we can make a DELETE request to delete a item.

The Todo component takes a todo prop, which is optional.

We have the default method to return the default value for it.

The v-if in the template checks if the todo.id exists.

If it does, then it’s displayed so we can call delete to delete the todo item.

We also have a @subnmit.prevent directive to let us submit our form and run the save method.

Next we work on the App.vue file.

We write:

<template>
  <div>
    <Todo @saved-todo="getTodos"></Todo>
    <Todo @saved-todo="getTodos" v-for="t of todos" :todo="t" :key="t.id"></Todo>
  </div>
</template>

<script>
import axios from "axios";
import Todo from "./components/Todo.vue";
const APIURL = "http://localhost:3000";

export default {
  name: "App",
  components: {
    Todo,
  },
  data() {
    return {
      todos: [],
    };
  },
  beforeMount() {
    this.getTodos();
  },
  methods: {
    async getTodos() {
      const { data } = await axios.get(`${APIURL}/todos`);
      this.todos = data;
    },
  },
};
</script>

We add the Todo component that we created earlier.

The first one is for adding the todo item.

The getTodos method lets us get the todo items.

It’s run when the saved-todo event is emitted.

We listen to the event with @saved-todo on the template.

Also, we call getTodos in the beforeMount hook so that we can get the todos when the page loads.

Running Our App

Once this is done, we can run our app.

We first go into the backend folder and run:

npm start

Then we go into the frontend folder and run:

npm run dev

Once that’s done, we can go to http://localhost:3001 to see the app.

Now we should see:

when we go to http://localhost:3001 and when we do something in our app, we see something like:

logged in Sematext.

It’ll log your activities.

Conclusion

Using Sematext with an Express app is easy.

We just use the sematext-express-agent package to let us log with it.

Creating a Vue front end is also easy.

Vue 3 is the up and coming version of Vue. It’s almost ready for production.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *