DEV Community

Cover image for Build Instagram Using TypeScript, Node, Express and Vue - Part 1
calvintwr
calvintwr

Posted on • Edited on

Build Instagram Using TypeScript, Node, Express and Vue - Part 1

This is tutorial 1 of a 5-part tutorial, but each tutorial can be read in isolation to learn various aspects Node+Express+TypeScript+Vue API/Vue web app set up.

At the end of this 5-part tutorial, you will learn to build an app like this:

Web app tutorial using Node, Express and Vue in TypeScript

Looking to learn mobile/desktop apps? The skills and concepts here are fundamental and re-usable for mobile apps (NativeScript) or desktop apps (Electron). I may cover them as a follow-on.

Navigation to other parts (you are at part 1)

  1. Setting up Node and Express API with TypeScript
  2. Setting up VueJs with TypeScript
  3. Setting up Postgres with Sequelize ORM
  4. Basic Vue templating and interaction with API
  5. Advanced Vue templating and image uploading to Express

Introduction

All good apps should start from a rock-solid base, which is what this tutorial about, illustrated through building a very simple photo sharing app, instead of a Todo (which really doesn't show much). Through these tutorials, you will learn TypeScript, Node, Express and VueJS, using versions as bleeding edge as it can get at the time of this post (some pre-releases where practicable).

*Sadly, Deno was considered but is still too early and to use. However, when the time comes, you are likely able to switch to Deno and reuse much of your API codebase if you generally follow best practices in this tutorial. You will be able to re-use all your view coding as it is not coupled to the API.

To be completely honest, Instagram can't be built in a single tutorial, so admittedly the title of this post is an exaggeration. We will call this project "Basicgram".

Get your repo

You can start building by cloning and checking out tutorial-part1 branch:

git clone https://github.com/calvintwr/basicgram.git
git checkout tutorial-part1
Enter fullscreen mode Exit fullscreen mode

Folder Structure

Folders will be split into "api", which will run a Node+Express set up, and "view", which will run a Vue+Webpack set up.

Express/VueJS Folder Structure

Get started - Installing Express (API engine)

npx express-generator --view=hbs
Enter fullscreen mode Exit fullscreen mode

I opted for Handlebars (hbs) as the view engine because it looks like HTML so you won't need to learn new templating syntax. But you hardly will use since we will only be using Express for API service -- but it's there for you when you need it.

We will use the latest Express 5.0 (pre-release) and update all module versions, so edit the package.json file:

{
  "name": "api",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.5",
    "debug": "~4.1.1",
    "express": "~5.0.0-alpha.8",
    "hbs": "~4.1.1",
    "http-errors": "~1.7.3",
    "morgan": "~1.10.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Fire it up and see if it all works

npm install
npm start
Enter fullscreen mode Exit fullscreen mode

Go to localhost:3000 and express should greet you.
Express Welcome Page

Express Routings

One of the first Express things you want to get is express-routemagic, which automatically require all our routes instead of declaring them file by file (you will see huge Express apps and their tower of routing codes which doesn't make sense). So just get routemagic, problem solved.

npm install express-routemagic --save
Enter fullscreen mode Exit fullscreen mode

We will replace the routing requires:

var indexRouter = require('./routes/index')
var usersRouter = require('./routes/users')
app.use('/', indexRouter)
app.use('/users', usersRouter)
Enter fullscreen mode Exit fullscreen mode

With:

const Magic = require('express-routemagic')
Magic.use(app, { invokerPath: __dirname }) // need `invokerPath` because we shifting Express into a `src` folder.
Enter fullscreen mode Exit fullscreen mode

That's it, you will never need to worry about routings. Let's move on.

Converting to TypeScript

TypeScript provides quite a lot of useful features to build better code. You can google its benefits. It has its downsides too, especially being more tedious and having to deal with non-typescript packages (there are many useful and time-proven ones, but didn't see the need to port themselves over to TypeScript syntax). Throughout this tutorial, figuring how to convert some JS syntax to TypeScript were either painful or close to impossible. But well, we soldier on.

Since now we need to compile our TS to JS for Node runtime, we will need a few steps to get there.

1. Pack your Express into a "src" folder like this:

Express TypeScript File Structure

And also notice that "app.js" is renamed to "app.ts". We will start with this and leave the rest alone for now. Baby steps.

Tip: I think it is OK to have mixed codebase. Not everything needs to be in TypeScript, plus you will be missing out a lot of really good JS modules. But I'm sure this statement invokes agitation amongst TypeScriptors. Oh well, make your case below.

2. Install TypeScript package and set up configurations

Install TypeScript (note: all npm commands are to run in the basicgram/api folder. api and view are technically two different apps. If you run npm in basicgram, you will mixed their node_modules and other configurations up.)

Setting up the TypeScript compiler

npm install typescript --save-dev
Enter fullscreen mode Exit fullscreen mode

Set up the tsc command in package.json:

"script": {
    "start": "node ./bin/www", // this came default with express, but we will change it later.
    "tsc": "tsc"
}
Enter fullscreen mode Exit fullscreen mode

And initialise tsc which will generate a configuration file:

npx tsc --init
Enter fullscreen mode Exit fullscreen mode

tsconfig.json will now appear in basicgram/api. This controls the compiler's behaviour. There's generally 2 default behaviours we want to change:

  1. TSC by default outputs ES5, which is really unnecessary for Node, being a server-side runtime (if what is stopping you from upgrading Node are your old apps, see Node Version Manager).

  2. It will search compile all .ts files inside of basicgram/api and produce .js alongside it, which really isn't what we want.

So we make the following changes:

{
    "compilerOptions": {
        "target": "ES6", // you can go for higher or lower ECMA versions depending on the node version you intend to target.
        "outDir": "./dist" // to output the compiled files.
    }, "include": [
        "src" // this tells tsc where to read the source files to compile.
    ]
 }
Enter fullscreen mode Exit fullscreen mode

Now let's try out our command:

npm run tsc
Enter fullscreen mode Exit fullscreen mode

You will see errors like:

src/app.ts:21:19 - error TS7006: Parameter 'req' implicitly has an 'any' type.

21 app.use(function (req, res, next) {
Enter fullscreen mode Exit fullscreen mode

That's mean TypeScript works, and it's telling you app.ts -- which is still in Javascript -- has type safety violations, rightfully so. And so we start the conversion.

3. Code conversion and type declarations

First, we need to install type declaration for all modules. Just go with me first, I'll explain what this is all about later. They are named "@types/[modulename]". Whether they are available depends on if the package owner has made it. A lot of them didn't really bothered. In any case, we are only going to do it for node and express as an example, while skipping over type-checking for other modules using // @ts-ignore.

npm install @types/node
npm install @types/express
Enter fullscreen mode Exit fullscreen mode

And convert the app.ts into this:

(Note: Use of @ts-ignore is not recommended, and only for the purposes of this demo.)

// @ts-ignore
import createError = require('http-errors') // change all `var` to import
import express = require('express')
import { join } from 'path' // this is a Node native module. only using #join from `path`
// @ts-ignore
import cookieParser = require('cookie-parser')
// @ts-ignore
import logger = require ('morgan')
// @ts-ignore
import Magic = require('express-routemagic')
const app: express.Application = express() // the correct type declaration style.
// view engine setup
app.set('views', join(__dirname, 'views'))
app.set('view engine', 'hbs')
app.use(logger('dev'))
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(express.static(join(__dirname, 'public')))
Magic.use(app, { invokerPath: __dirname }) // // need to use `invokerPath` because we are not in api's root dir.
// catch 404 and forward to error handler
app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { // type declaration, and changed to use arrow function
    next(createError(404))
})
// error handler
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
    // 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
Enter fullscreen mode Exit fullscreen mode

What is app.use? It is Express's way of using "middleware" (functions that will run and be given the HTTP request to "read" and do something to it). And it is sequential, and one way is to imagine a HTTP request "falling" through your middlewares: In this case it will first pass through logger('dev'), which logs the request on your terminal, then express.json() which parses the request into json... and so on so forth sequentially.

TypeScript Basics Explanation

The @types/express module you have installed are TypeScript declarations for Express objects. Declarations are like a dictionary -- it explains what something is or isn't.

If you refer lower down in app.ts, the block of // error handler code shows how this "dictionary" is applied to function arguments:

(err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => { ... }
Enter fullscreen mode Exit fullscreen mode

What it means is that, the req argument are "of the same type and -- for a lack of better word -- form" with Express' Request object/prototype (I refuse to use the word "class", because Javascript is irrefutably classless).

So within the function, if you try to use Request as a type that it isn't, or if you try to invoke a method that Request does not have, TypeScript will bitch about it.

(req: express.Request) => {

   req.aMethodThatDoesNotExist() // red curlies underlines, and will not compile.

   if (req === 'someString') {} // TypeScript will tell you this is always false.

})
Enter fullscreen mode Exit fullscreen mode

All that, is in essence a very basic explanation of how TypeScript type-checks your code.

And now if you run npm run tsc again, you should get no errors.

4. Copy all files to "./dist"

TSC will only compile .ts files, rightfully so. But you need to copy the rest of the files over, including those .js files that you either don't intend to convert, or will convert later (that's the beauty, you don't need to always OCD everything to TypeScript -- not all code is worth your time). tsc doesn't seem to provide a good way (see issue here), so we will use the cpy-cli and del-cli modules:

npm install cpy-cli del-cli --save-dev
Enter fullscreen mode Exit fullscreen mode

Set up the npm scripts in package.json.

  1. A prebuild script that uses del shell command (from del-cli module) to delete the old "./dist" folder:
"prebuild": "del './dist'"
Enter fullscreen mode Exit fullscreen mode
  1. A postbuild script that uses cpy shell command (from cpy-cli module) to copy remaining files over:
"postbuild": "cpy --cwd=src '**/*' '!**/*.ts' './../dist' --parents"

// --cwd=src means the Current Working Directory is set to "./src"
// '**/*' means all files and folder in the cwd.
// '!**/*.ts' means excluding all typescript files.
// './../dist' means "basicgram/api/dist", and is relative to "src" folder
// --parents will retain the folder structure in "src"
Enter fullscreen mode Exit fullscreen mode

And your scripts in package.json will be:

{
    "scripts": {
        "start": "node ./dist/bin/www",
        "build": "npm run tsc",
        "prebuild": "del './dist'",
        "postbuild": "cpy '**/*' '!**/*.ts' './../dist' --cwd=src --parents",
        "tsc": "tsc"
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, just to check everything is working, go to "src/routes/index.js" and change title from Express to Express in TypeScript:

res.render('index', { title: 'Express with TypeScript' })
Enter fullscreen mode Exit fullscreen mode

Build and run it:

npm build
npm start
Enter fullscreen mode Exit fullscreen mode

Running express compiled from TypeScript

5. Setting up auto re-compiling

For development, it's inefficient to keep running npm build and npm start. So we are going to use nodemon to auto restart the server on file changes, and ts-node to execute the TypeScript files as though they are Javascript (note: this is intended for development environment and does not output to ./dist):

npm install nodemon ts-node --save-dev
Enter fullscreen mode Exit fullscreen mode

Add the following to package.json:

"scripts": {
    "dev": "nodemon --ext js,ts,json --watch src --exec 'ts-node' ./src/bin/www"
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

--exec: We use --exec flag because nodemon will not use ts-node, instead will use node if the entry file is not ".ts". In this case www is not.
--ext: When --exec is used, we also need to use --ext to manually specify the files to watch for changes.
--watch: This defines which folder nodemon will watch for changes to do a restart.
(Credits to this video)

Run your dev server:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Your API is all fired up! Make some changes to see how nodemon auto re-compiles. See Part 2 to set up your view engine with VueJS in TypeScript.

Endnotes:

  1. Getting Started Using TypeScript with Node.js and Express
  2. Typescript compiler file copy issue
  3. Video: TypeScript Setup With Node & Express

Top comments (5)

Collapse
 
rockykev profile image
Rocky Kev

Wow - just in time! I wanted to tackle a bigger project with vue + typescript. I haven't used Express since i learned it a few years ago, so excited to work on that. And funny you mentioned Deno - that was on my to learn list too! Had it been ready, this project would have been the trifecta of every single thing on my to learn list!

Will start this tonight and excited to share any thoughts! You rock!

Collapse
 
calvintwr profile image
calvintwr • Edited

Hey there, thanks for the kind words. I was thinking I put together all that I have learnt from other people's tutorials, and give back. And in a way, it was just all a bunch of notes I made as I was coding stuff put together.

I do have to write my own libraries to solve problems that I can't find libraries to solve. But you are welcomed to criticise those or offer alternatives. Cheers mate, chat me up and I will be happy to help.

Oh btw, if you learn Node+express, you are learning parts of Deno. There's already a few Express-lookalike Deno modules. So don't worry about it!

Collapse
 
rockykev profile image
Rocky Kev • Edited

Hey @calvintwr - I wanted to give you an update. I've been tackling this bit by bit. I reached step 3 and the whole Postgres bit got me stuck. The Postgres setup is tripping me up. I'm going through a quick course on it before I continue.

Either way - I've been saving feedback for little things I noticed. But as of the middle of step 3, I've been having a lot of fun in this new world! Really huge thanks for the write-up and I'll continue pushing in the next few days.

Thread Thread
 
calvintwr profile image
calvintwr

Hey there! What about Postgres that’s getting you stuck? Maybe I can help.

Collapse
 
Sloan, the sloth mascot
Comment deleted