2022/March 2022

Setting up typescript MEN (Mongo DB, Express, Node)

hajinny 2022. 3. 5. 16:20

1 What's up?

I've started working on SOFTENG 701 group project. It's basicallly a 20 human university project that aims to create a web application (well, it can be anything that uses java/js/python but everyone wants to do MERN). It's worth noting how only tiny proportion of the team are familiar with the stack - that means the more you know, the more you can shine really. If you are second/third year reading this, I recommend having a look at http://fullstackopen.com/en/, as you can build a whole MERN full stack project using this. 

 

I am part of the backend team, and I volunteered to create a project setup for it. I figure that it's useful to document the process here so that it's easy to see what needs to be done later when I try it.

2 Setting up Typescript project

Here's the project structure to begin with:

In the backend folder's root, run npm init. You will need following dependencies for this project:

devDependencies: @types/node, ts-node, typescript 

dependencies: cors, dotenv, express, mongoose (these aren't needed until next section, but you are doing MERN so just install them)

 

Then add "start": "ts-node src/index.ts" for npm start.

 

Here's a fairly good tsconfig.json that allows you to use modern syntax of es2017 (like async, promise, import from)

{
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "lib": [
      "dom",
      "es6",
      "es2017",
      "ES2015",
      "esnext.asynciterable"
    ],
    "skipLibCheck": true,
    "sourceMap": true,
    "outDir": "./dist",
    "moduleResolution": "node",
    "removeComments": false,
    "noImplicitAny": false,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noImplicitThis": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "resolveJsonModule": true,
    "baseUrl": "."
  },
  "exclude": [
    "node_modules"
  ],
  "include": [
    "./src/**/*.ts"
  ]
}

You can create a src/index.ts file then run npm start to see that transpiling/running/hot reloading works right away. Very handy. 

3 Set up express

Express is very simple to set up.

import express from 'express';
import cors from 'cors'

const app = express();
const port = 3000;

app.use(express.urlencoded({extended: false}))
app.use(cors())
app.use(express.json());

app.listen(port, ()=>console.log(`App server listening on port ${port}!`))

 

Now, here's a cool trick that seems to be emerging lately. Imagine we want to set up /api/students and /api/teachers. We can do it like this:

 

src/routes/api/

 

src/index.ts:

import routes from './routes'*

app.use('/', routes) // direct requests to routes router that come with '/' prefix 

import express from 'express';
import routes from './routes';
import mongoose from 'mongoose';
import config from "./utils/config";
import cors from 'cors'

mongoose.connect(config.MONGODB_URI)
    .then(()=>{
        console.log("connected to MongoDB")
    })
    .catch((err)=>{
        console.error("error connected to mongodb:", err.message)
    })

const app = express();
const port = 3000;

app.use(express.urlencoded({extended: false}))
app.use(cors())
app.use(express.json());
app.use('/', routes)

app.listen(port, ()=>console.log(`App server listening on port ${port}!`))

 

src/routes: (now responsible for handling requests to root)

import api from './api'

app.use('/api', api) // direct requests that come with '/api' prefix 

const express = require('express');
import api from './api'
const Router = express.Router();
Router.use('/api', api);

export default Router

 

src/routes/api/index.ts:

const express = require('express')
const router = express.Router();

import personRouter from './person'

router.use('/person', personRouter)

export default router;

src/routes/api/person.ts:

import Person from '../../models/person'
import {Request, Response} from 'express'

const express = require("express");
const router = express.Router();

router.get('/', async (req: Request, res: Response)=>{
    res.json({"someString": "hello world"});
})


router.post('/', async (req: Request, res: Response)=>{
    const body = req.body;
    try {
        const newPerson = new Person({
            name: body.name,
            organisation: body.organisation,
        });

        await newPerson.save()
    } catch (e){
        console.error(e)
        await res.status(400).json({
            error: e.toString()
        })
    }
    res.status(200).end()
})

export default router

 

*note: './routes' is a folder, and when I import from a folder, javascript knows I'm importing from 'routes/index.js'.

 

4 MongoDB

Create a model 'src/models/person.ts':

const mongoose = require('mongoose');

const personSchema = new mongoose.Schema({
    name: {
        required: true,
        type: String
    },
    organisation: {
        required: true,
        type: String
    }
})

export default mongoose.model('Person', personSchema)

 

5 Config

Finally, under src/util, I created config file that uses dotenv. dotenv references .env file at the root.

import dotenv from 'dotenv'
dotenv.config()

const PORT = process.env.PORT || "port not found"
const MONGODB_URI = process.env.MONGODB_URI || "url not found"

const config = {
	PORT,
	MONGODB_URI
}

export default config