Simple MongoDB Crud, Typescript React application

A second example of a minimal fullstack application to help me to extend my fullstack knowledge. The first application used SQLite. The example in this post does exactly the same thing, the main differences are that MongoDB is used to store the slogans and I extended the React side with Typescript.

Minimal Application

To learn more full stack I first created a minimal React application to connect to a minimal Rest API.

slogans app
Slogans app React part

This blog post will focus on my experience with MongoDb and Typescript for more explanation about the application check the previous blog post. Start the application with the following steps

  1. Create a local folder for MongoDB sudo mkdir -p /data/db
  2. Set the owner for the folder sudo chown -R $USER /data
  3. install mongoDb follow the guide on https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/
  4. clone the repo: https://github.com/jeroenoliemans/mongodb-react.git
  5. start mongoDb in te terminal with mongod
  6. and run the command npm install && npm run start
  7. check the application at http://localhost:3000/
  8. and the API should run at http://localhost:3030/api/slogans

About the server side code

The server side code is divided into two node files with the .mjs extension to make use of es6 inside node: server/db.mjs and server/index.mjs

The db.mjs has functions to return the MongoDB connection and to reset and instantiate the collection

The index.mjs is the actual minimal backend for the application, which has all the endpoints for the CRUD actions for the slogans. It keep the MongoDb connection open until the application is closed. As the documentation tells this is the most performing way. All operations of the mongo node client return Promises which makes it use to use. I used a async function for the add endpoint to prevent nesting Promises. I like async functions, hence I do not know yet if they are easier to read and reason about as Promises.

Add endpoint with related async function

app.post("/api/slogan/", (req, res, next) => {
    if (!req.body.slogan) {
        res.status(400).json({"error": "No slogan specified"});
        return;
    }

    try {
      let insertedItem = insertOne(db, 'slogans', req.body.slogan);

      return res.json({
        "message":"success",
        "data": insertedItem
      });
    } catch (error) {
      res.status(400).json({"error": error})
    }
  });

...

// async functions more to use when multiple async ops are required
const insertOne = async (db, collection, slogan) => {
  let collectionCount = await db.collection(collection).find({}).count()
  return await db.collection(collection).insertOne({id: (collectionCount + 1), slogan: slogan});
}

Experience with MongoDB

I did have experience with editing mongo db documents with special tooling like Robo3T and NosqlBooster. This was the first time for me to delve deeper into mongo. I must admit that for a JavaScript developer the experience feels more natural as opposed to SQL.

It took a while before I had some knowledge of the basic best practices.Like should I close the connection after each operation, and setting a custom id of should I use the ObjectID provided by Mongo itself. For the latter question I have the feeling that I took the wrong decision and should have used the ObjectID as the main ID. Leason learned 😉
Remember that ObjectID is an object itself when you choose for ObjectID as reference for the documents.

Experience with Typescript

The effect was minimal, since I was reusing an existing application and adding Typescript to it. Nevertheless I can see the benefit hen building a new application while receiving type errors.

I do not think that hints for default type will help me a lot since I do currently not encounter lot of issues with those. But defining type modals, proper DTO’s with types, function parameters eg… that would help alot. I think it could improve coding speed as well since you receive focused information about the code you already have written.

Typescript in functional component

type SloganProps = {
    slogan: string, 
    dbId: number, 
    handleSloganRemove: (dbId:number) => void, 
    handleSaveSlogan: (dbId:number, sloganText:string) => void
}

const Slogan = ({ 
    slogan, 
    dbId, 
    handleSloganRemove, 
    handleSaveSlogan
}: SloganProps) => {
    const [sloganText, setSloganText] = useState<string>(slogan);
    const [edit, setEdit] = useState<boolean>(false);

    const handleRemove = (dbId: number) : void => {
        handleSloganRemove(dbId);
    };
...

Personal takeaway

MongoDB for a JavaScript developer feels more natural than SQL, with the latter being able to transfer more logic to the database. And of course having the (dis)advantage of having relations. In a lot of things NoSQL and SQL are absolutely different, but for simple apps both are suitable.

Converting a simple application as this to typescript took less time than expected. For new application I would seriously consider enforce TypeScript. I still do not know if the trade of is worth it. I think it also depends on the type of application; complex fintech app, would not consider to start it without TypeScript. Application displaying items for marketing purposes not so sure is TypeScript would be a requirement…

Minimal Fullstack CRUD example

Since long I wanted to extend my fullstack knowledge, I think that even for a frontender it is beneficial to have backend knowledge.

Minimal Application

To learn more full stack I first created a minimal React application to connect to a minimal Rest API.

slogans app
Slogans app React part

The minimal application stores slogans to save the world. Since this is a mostly a front-end blog I will assume the the React part will be clear. This blog post will focus on the back-end part of this small application. Start the application with the following steps

  1. clone the repo: https://github.com/jeroenoliemans/sqlite-react.git
  2. and run the command npm install && npm run start
  3. check the application at http://localhost:8055/
  4. and the API should run at http://localhost:8044/api/slogans

Application structure

You can see the main structure of the application below, leaving out many files and only listing the most important files of the application in the structure overview.

  • server
    • database.js: initializes the sqlite db
    • server.js: rest endpoints created with express.js
  • site
    • services.js: implements the correct calls to the API with values from the front-end
    • App.js: the main React file uses services.js

Database.js

The database.js file connects to the sqlite db, if the db is not yet available it creates it with the name set in the code. The sqlite db file will be saved in the root

const sqlite3 = require('sqlite3').verbose();

const DBSOURCE = 'db.sqlite';

let db = new sqlite3.Database(DBSOURCE, (err) => {

If the db and the table is not yet created than SQL will create it with a simple statement since it has only one field, besides the primary key. Once created 2 items will be inserted.

CREATE TABLE slogan (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    slogan text
);

Server.js

The server.js file can be seen as the backend of the application, or as the API. It is totally based on the express.js framework. The first part initializes the express app en set the needed response headers.

// body parser
app.use(express.json());

// set headers
app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*"); // wildcard, only for localhost
    res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
  });

For production use the headers will not be sufficient. And need to be more strict.

For the rest services.js has all the endpoints for the API, each endpoint is a express.js function (app.get(), app.put()…) which executes some SQL statement in the database and returns an error or a success JSON response with optional data attached. See the method for fetching all slogans (http://localhost:8044/api/slogans)

// get all slogans
app.get("/api/slogans", (req, res, next) => {
    const sql = "select * from slogan";
    let params = [];
    db.all(sql, params, (err, rows) => {
        if (err) {
          res.status(400).json({"error":err.message});
          return;
        }
        res.json({
            "message":"success",
            "data":rows
        })
      });
});

This in a nutshell is all that there is for the serverside part of this application. Up next will be the fetching of the front-end part of the application.

Services.js

Services.js is an abstraction of the calls to the api. For deep control I just created a helper method around the battle-tested, though awfully named XMLHttpRequest. This helper method returns a Promise and makes it easy to apply the HTTP request headers in one place.

// ajax request helper
function get(url, type = 'GET', data) {
    return new Promise((resolve, reject) => {
        let req = new XMLHttpRequest();

        req.open(type, url, true);
        req.setRequestHeader('Accept', 'application/json');
        req.setRequestHeader("Content-type", "application/json");
        
        req.onload = () => {
            if (req.status == 200) {
                resolve(req.response);
            } else {
                reject(console.log(req.statusText));
            }
        };

        req.onerror = () => {
            reject(console.log('Network Error'));
        };

        (type === 'POST' || type === 'PUT') ? req.send(JSON.stringify(data)) : req.send();
    });
}

The methods using this helper can now be made very simple.

const services = {
    getSlogans: () => {
        return get(`${apiDomain}api/slogans`);
    },
    addSlogan: (slogan) => {
        return get(`${apiDomain}api/slogan`, 'POST', {slogan: slogan});
    },
...

These methods can now be used by any React/ JavaScript component which imports this service file. For example in the main application file App.js.

App.js

App.js is the main React file which most front-end developers will be familiar with. In this file I made some develop shortcuts to redundantly call getSlogans, after each update, delete… calls. This keep the application really simple to reason about with a single source of truth. Perform a altering CRUD operation the refresh the state thus the whole view again from the database. For production use (with a large dataset) this could result in a bad performing application.

// the state to store the result from the API
const [slogans, setSlogans] = useState([]);

    const getSlogans = () => {
        services.getSlogans()
        .then((response) => {
            let responseObject = JSON.parse(response);
            setSlogans(responseObject.data);   
        }, (error) => {
            console.log(error)
        });
    }
    
    const addSlogan = (sloganText) => {
        services.addSlogan(sloganText)
        .then((response) => {
            getSlogans();
        }, (error) => {
            console.log(error)
        });
    };
...

Wrap up

Personally I think that is is valuable for every front-end developer to create a small end-to-end application. It will definitely help communicating and understanding with other developers. And to become a more T-shaped front-end developer. For myself I have learned a lot and hope to find time to connect the same application again but then with an external db for example mysql or postgresql. Happy developing 👍