Setting up typescript MEN (Mongo DB, Express, Node)
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