How to Create a React.js Support Ticketing System Using MongoDB

In the last article I wrote we talked about how to create a Redux-Form inside a React/Redux application. Now I want to put that form to work for us, and create a simple support ticketing system using a Node.js/Express server and Mongoose to talk to a MongoDB. This will get us some good working knowledge of how to use Redux-Form, but more importantly, we'll work with Mongoose and API's in general.

Warning: This tutorial only covers specific elements of the MERN stack process. As such it won't go into much detail on actions, action types, reducers, or even Redux and React. What this tutorial will do is show you how to use Mongoose to save information into a MongoDB and use Axios to send information from your client to your API. If that's alright with you, let's get buckled in!

You'll be able to view the entire tutorial at this gist.

What do we need to get started?

You're going to need both an API and a Client side application for this project. You can get started with creating your first API with this tutorial and you can also break into a simple "hello world" React app with this one.

You are also going to need a MongoDB to work with. You can set one up locally, but I would recommend heading over to mLab where you can create a free sandbox to play with.

Defining our Mongoose Schema

I personally like to start new features on the server side. This gives you a good overview of how you need/want your data structured and also tells you the API endpoints to send that data too. First things first we need to define a schema for our tickets. A MongoDB schema is defining the structure of the documents that we want to store in our collection (database). Open up a new file models/tickets.js. Inside this file, we want to import some basic dependencies, define them, and then export the TicketSchema.

const mongoose = require('mongoose'),  
      Schema = mongoose.Schema;

const TicketSchema = new Schema({  
    //schema structure will go here.
});

module.exports = mongoose.model('Tickets', TicketSchema);  

This should be pretty self-explanatory, but if not you can view the official docs here that do a good job of explaining it. Now we actually want to put in some information that we want to store in this document. At it's simplest you'll define the "fields" in your document as an object and declare a type. We also want to make sure that we set a required property for things like the user's email and their message. I've gone ahead and populated a schema for this tutorial:

const TicketSchema = new Schema({  
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true
  },
  status: {
    type: String,
    required: true
  },
  message: {
    type: String,
    required: true
  }
}, {
  timestamps: true
});

The timestamps parameter I specified at the end is a nifty little tool that will store information like when the ticket was created, and when it was modified. It's all handled directly inside MongoDB.

Creating a controller to add the ticket to the database

Our API endpoint is going to point to a function inside a controller file that will save the data received from our client into our mongoDB. Go ahead and create a controller for our tickets, controllers/_ticket-control.js. Inside here we will bring our Tickets schema and start working with the data we plan to receive from our client. We'll also set this controller to 'use strict' so that it can catch some common coding bloopers, as well as allow us to use let, a replacement for var.

'use strict';

const Tickets = require('../models/tickets');

exports.createTicket = function(req, res, next) {  
  const name = req.body.name;
  const email = req.body.email;
  const message = req.body.message;

  if (!name) {
    return res.status(422).send({ error: 'You must enter a name!'});
  }

  if (!email) {
    return res.status(422).send({ error: 'You must enter your email!'});
  }

  if (!message) {
    return res.status(422).send({ error: 'You must enter a detailed description of what you need!'});
  }

  let ticket = new Tickets({
    name: name,
    email: email,
    status: "Open",
    message: message
  });

  ticket.save(function(err, user) {
    if(err) {return next(err);}

    res.status(201).json({ message: "Thanks! Your request was submitted successfuly!" });
    next();
  })
}

First thing inside our function, we simplify the req data that we received by saving what we anticipate being in them to variables for later use.

You'll notice that you then incorporated some error handling to make sure that the client is sending all of the information that is going to be needed. Should these conditions fail to be met, it will simply stop the function and return a 422 error and describe what was missing.

Next, we define a new ticket and fill in the data. With this new ticket object we use the mongoose function .save to save it into our document. Some more error handling follows this if something should go wrong, but if it's a success we send the 201 and a success message.

Configuring Mongoose with our MongoDB

We're going to take a side step here to set up mongoose so that it can actually communicate with our database. It's pretty simple and only requires adding a couple of lines to your server/index.js file. On your mLab dashboard, you can select your database, and view the connection information. You may need to create a user for the collection. This connection information will include a username, password, and database location and name. Copy those and save them into your server/index.js.

Make sure to use strong security methods to secure your database for production, like using environment variables and storing the reference to them in a separate file. For the sake of this tutorial though I've included it all into our server/index.js file.

// Importing Node modules and initializing Express
const express = require('express'),  
      app = express(),
      router = require('./router'),
      cors = require('cors'),
      bodyParser = require('body-parser'),
      mongoose = require('mongoose'),
      config = require('./config/main');

//always use environment variables to store this information
//and call it in a seperate file.
const databse =   'mongodb://username:password@location.mlab.com:port/database'

// Database Setup
mongoose.connect(config.database);

// Import routes to be served
app.use(bodyParser.urlencoded({ extended: false }));  
app.use(bodyParser.json());  
app.use(cors());  
router(app);

// Start the server
app.listen(config.port);  
console.log('Your server is running on port ' + config.port + '.');

Also be sure that you're installing and importing mongoose:

npm install --save mongoose  

Time to make the API endpoint that our client sends data to

At this point, you should be familiar with creating routes in your API. The only difference here is that we want to be sure to bring in our _ticket-control, and call the proper function from there:

//import dependencies
const express = require('express'),

// import controllers
const _ticketController = require('./controllers/_ticket-control');

module.exports = function(app) {

  const ticketRoutes = express.Router();

  apiRoutes.use('/tickets', ticketRoutes);

  ticketRoutes.post('/create-new-ticket', _ticketController.createTicket);

  app.use('/api', apiRoutes);
}

And with that, our API is all set up to receive our new ticket from a troubled user!

Creating a form and sending the data to an action

Now it's time to set up a simple Redux-Form on our client side. Create the file client/components/ticket_form.js and pass the forms properties into the function that calls our action. We'll start making this action called submitTicket next.

import React, { Component } from 'react';  
import { Field, reduxForm } from 'redux-form';  
import { connect } from 'react-redux';  
import * as actions from '../../actions';

const form = reduxForm({  
  form: 'tickets'
});

const renderField = field => (  
    <div>
      <div className="input_container">
        <input className="form-control" {...field.input}/>
      </div>
      {field.touched && field.error && <div className="error">{field.error}</div>}
    </div>
);

const renderTextArea = field => (  
  <div>
    <div className="input_container">
      <textarea {...field.input}/>
    </div>
    {field.touched && field.error && <div className="error">{field.error}</div>}
  </div>
);

class TicketForm extends Component {  
  handleFormSubmit({type, message}) {
    this.props.initialize('');
    this.props.handleSubmitTicket({type, message});
  }

  render() {
     const { handleSubmit } = this.props;

     return (
       <div>
         <form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>

           <label>Name:</label>
           <Field name="name" type="text" component={renderField}/>

           <label>Email:</label>
           <Field name="email" type="email" component={renderField}/>

           <label>Tell us in detail about the problem:</label>
           <Field name="message" type="text" component={renderTextArea}/>

           <button action="submit" className="button">Save changes</button>
         </form>
       </div>
     )
   }
 }

 function mapStateToProps(state) {
   return {
     formValues: state.form
   };
 }

 export default connect(mapStateToProps, actions)(form(TicketForm));

Creating an action to send our form data

Let's create an action handler that will send the formProps to our API endpoint. Inside you client/actions/index.js file (or wherever you're storing your action creators) let's build out a post request with Axios. Make sure that your action types have the appropriate actions available and import them so that your action can dispatch it.

import axios from 'axios';  
import { SUBMIT_TICKET, ERROR_RESPONSE } from './types';

// server route
const API_URL = 'http://localhost:3000/api';

export function errorHandler(error) {  
  return {
    type: ERROR_RESPONSE,
    payload: error
  };
}

export function submitTicket({name, email, message}) {  
  return function(dispatch) {
    axios.post(`${API_URL}/tickets/create-new-ticket`, {name, email, message}
    )
    .then(response => {
      dispatch({
        type: SUBMIT_TICKET,
        payload: response.data
      });
    })
    .catch(response => dispatch(errorHandler(response.data.error)))
  }
}

We use Axios here to communicate with our server. By defining the API_URL and directing it to the route we created earlier we are able to pass data to the function that will save this data in our database. The Axios post request sends an object containing the name, email, and message. Then when it receives the 201 response from our server it dispatches the action type and passes the payload to our reducers to update the app state with the success message.

All done!

This tutorial covered a lot of topics pretty quickly. You created a schema that defined what your support tickets were going to look like, then you allowed an API call to save a new ticket into your Mongo database. You also created a connection between your client and your server with Axios that sends your Redux-Form properties. All in all a pretty good day!

This concept can be manipulated in a number of ways to handle various situations, like saving just about anything to your database. Using this method to save user information is not recommended though as it does not include any encryption for their passwords. It is nonetheless an efficient way to handle simple (nonreal-time) messages, events, contacts, or support tickets.

Thanks for reading, if you have questions don't be afraid to ask below. Your feedback is appreciated as well so that I can continue to improve these tutorials. If you have any suggestions for topics, please leave those as well!

Until next time, happy coding!

You can view the source code for this entire tutorial at this gist.

David Meents

React.js developer, web designer, and business owner.

Subscribe to David Meents

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!
comments powered by Disqus