Skip to content

Instantly share code, notes, and snippets.

@iamdejan
Last active January 9, 2022 03:33
Show Gist options
  • Select an option

  • Save iamdejan/49cdd80df02dca7aa0340e4b4b43e7cb to your computer and use it in GitHub Desktop.

Select an option

Save iamdejan/49cdd80df02dca7aa0340e4b4b43e7cb to your computer and use it in GitHub Desktop.
Make Your Own Web App

Make Your Own Web App

Why do I make this Gist? Because I want to:

  • document resources (mostly links) that are available on the internet on how to make a web app.
  • make sure others who read this (especially from non-programming background) understands how to convert idea into an app.

Table of Contents

Table of contents generated with markdown-toc

Learn How To Program

In this post, the programming tutorial will use TypeScript.

Introduction to TypeScript

What is TypeScript

TypeScript is a programming language that is used to program web applications. The main uniqueness is that it is one of few languages out there that compiles to JavaScript. This means that TypeScript can be run at JavaScript-compatible browser while offering new features. TypeScript compiler will (kind of) convert our code into JavaScript, so that we can run it on the browser.

We can try programming in TypeScript Playground before actually installing Node.js and program in your local computer.

Why Use TypeScript

TypeScript is the closest programming language to web development while being type-safe. Type-safe is a programming term that basically means the programming language disallows / discourages us to make type error*.

* Let's just say, the compiler loves us and wants to prevent us from making silly errors. More details later.

Input and Output

So, the first step to any programming language, is to try out how to give output (to terminal) and receive input to your program.

Let's start from the output first. In TypeScript Playground, try this:

console.log("Hello world");

See the LOG at the right part. We will see Hello world text appear. Congratulations! We just write your first program. We tell computer to show a text to the terminal (here the term is console).

How about the input? Here's the tricky part. TypeScript (along with JavaScript) is actually a web programming language, which means they originally do not receive input from terminal. So, how do we get the input? The answer: depends. On frontend, the input comes from form (or any text input) on HTML page. On backend, the input comes from HTTP call. So, the input will be explained later, as there is nothing we can do.

Variable and Data Type

In TypeScript, you can store a data in something called variable. A variable has 2 components:

  1. Value: The value of the variable. For example, a number variable with value of 5 means that number 5 (00000101) is stored in memory.
  2. Data type: determines how the data will be treated and how the data will be stored in the memory. There are several data types well known in TypeScript, such as number (to store integer and decimal), string (to store text), and boolean (to store conditional value, either true / false).

Depending on the whether we can change the value / not, there are 2 types:

  1. let: once the value is assigned, we can change the value.
  2. const: once the value is assigned, we cannot change the value.

Try typing this code on TypeScript Playground.

let a: number = 1;
console.log(a);

On the right side, it will show number 1. Now, in the code, change 1 to 5, then click Run. Now, we will see the number printed is 5.

Now, try clear the code & type this code:

let a: number = 5;
a = a + 2;
console.log(a);

I hope you see number 7. Why 7? Because we initially set 5 as the value. But in the next line, we change the value by current value added by 2. When the variable is printed, it will show new value.

Now, try this code instead:

const b: number = 3;
b = b - 1;
console.log(b);

Notice that:

  1. Before we even running the code, we already got error, in the form of red line below the variable b.
  2. If we insist on running the code, we will get error message like this: [ERR]: Assignment to constant variable.. That means, we cannot change the value of a constant.

But, we still can do normal operation with constant(s). Example:

const a: number = 3;
const b: number = 5;
console.log(a + b);

Notice that we did not change value of constant a and b, we are only adding both into a temporary variable (in the form of console.log method argument) and then print / show the result.

Operator and Operand

In mathematics, you often see these equations:

  • 2 + 2
  • 5 / 3
  • 2x - y

Well, in TypeScript, we will treat number similar to mathematics equation. For example, previously we write console.log(a + b), that means we add from variable a and b.The middle symbol (the addition symbol / +) is used to operate left and right number, in such a way that they produce a new number. This is called operator. The operand is the left and right symbol of the operator, in this case a and b.

There are at least 3 categories of operator in TypeScript. Those are:

  1. Arithmetic: used for math operations (+, -, *, /).
  2. Comparison: used to compare operands. Read more here.
  3. Assignment: used to assign value from right operand to left operand. Some operators are combination between pure assignment operator (=) and arithmetic operator. Read more here.

Iteration

Let's say I want to print Hello world text 3 times. How would I do that?

console.log("Hello world");
console.log("Hello world");
console.log("Hello world");

Well, this works. You will see Hello world text 3 times. But, you have to copy the code, didn't you? What if there are multiple lines of code that you have to copy? Suddenly that feels "un-natural" and "dirty", e.g.

let n: number = 1;

console.log(n);
n = n + 1;

console.log(n);
n = n + 1;

console.log(n);
n = n + 1;

Well, suddenly we have duplicate lines of code. What happens when we try to change the logic, e.g. adding description when printing?

let n: number = 1;

console.log(`this is ${n}`);
n = n + 1;

console.log(`this is ${n}`);
n = n + 1;

console.log(`this is ${n}`);
n = n + 1;

Suddenly, we have to change the code in 3 lines, which is... not what we want. The code suddenly feels unmaintainable. You do more work just to replace all of the printing logic. Well, time to learn for loop.

For Loop

For loop works by encapsulating your logic inside for block, and then the computer will execute that block until a condition is not met (a.k.a. false). Here's an example:

for (let i = 1; i <= 3; i++) {
  console.log("Hello world");
}

Let's analyze per part:

  1. for means for each of this iteration, I (the computer) will execute code inside the block. 2)let i = 1; We declare a variable called i, and set the value as 1. In this case, it's a common practice not to include the data type, as it will bloat the block, although you CAN add data type like usual (let i: number = 1;).
  2. i <= 3;: this is the condition that is being checked by the computer. Once this condition is not met, the loop stops.
  3. i++: add value of variable i by 1 at the end of each loop. This is to ensure that one time in the future, the state of the for loop code changes. If we did not change i value (or even decrease it!), we're stuck in infinite loop. What happens if we want to add by 2? Just change the code into i += 2.
  4. The rest is the block of code that will be executed each code.

In TypeScript, there are 2 keywords that often used within for loop block. Those are:

  1. break: if this code is executed, get out from the for loop block. Read more here.
  2. continue: skip the rest of the code, and continue to next iteration. Read more here.

While

What if we want to iterate while a condition is fulfilled? Well, there is such syntax in TypeScript. It's called while loop. Here's an example:

let score: number = 1;
while(score <= 5) {
    console.log(`current score = ${score}`);
    score = score + 1;
}

NOTE: if you wonder what backtick does, read more here. Try that code on TypeScript Playground. We will see 5 times current score is printed, along with changes in score variable value. Let's analyze each part:

  1. while means while a given condition is true.
  2. score <= 5 is a given condition. Note that this can be replaced with a boolean variable (as long as the variable's value changed, that's okay) / function (we will learn about function more).
  3. the code block that will be executed while the given condition is true / fulfilled.

Read more about while loop here.

Complex Usage

If you wonder how powerful is iteration feature (both for and while loop), you can read a post about Kruskal algorithm, Breadth-First Search algorithm, and Shunting Yard algorithm I wrote a while ago. Note that these posts are not aimed for beginners, since I used Rust language (a different programming language than TypeScript, albeit also a static and strict language), and these are not easy-to-understand algorithms.

Iteration in Other Programming Languages

Just to give you a picture of iteration in other languages. This is not a must read if you're hurry.

For loop is very common in programming languages, although there are variations:

  1. for(int i = 0; i <= 5; i++): C, C++, and Java variant. Notice that in C, C++, and Java, you write the data type at the left side of variable name.
  2. for i in range(5): this is Python style. Since Python is a duck-typed language, variable i is assumed to have a number type. range(5) means return array with length 5 that starts from 0 and with increasing value ([0, 1, 2, 3, 4]).

If you need a loop that runs until a specific condition in the middle of the loop, you will write while(true) {...} in TypeScript. However, in Rust, there is a cleaner way, which is using loop {...} syntax.

In C, C++, and Java, there is a version of while that is guaranteed to execute the block once, before checking the given condition. It's called do-while loop. One example is here.

Basic Data Structures

When we want to process more than 1 data with same / similar logic, it's better if we put those data into 1 collection. Then, we can use loop for the programming logic. What?

Example: We want to find square of several numbers (3, 14, 7, 11, 4). We can do this:

const a: number = 3;
console.log(`${a} squared is ${a * a}`);

const b: number = 14;
console.log(`${b} squared is ${b * b}`);

const c: number = 7;
console.log(`${c} squared is ${c * c}`);

const d: number = 11;
console.log(`${d} squared is ${d * d}`);

const e: number = 4;
console.log(`${e} squared is ${e * e}`);

But of course, that's unnecessary duplication. Let's use array!

Array

Array is a data structure (ordered and indexed) to store multiple data with same data type. Literally, it has to be same (with the "exception" of subclass), otherwise TypeScript will not compile. But JavaScript allows? That's because JavaScript is not ehm type-safe.

How to declare array? Simple. Using numbers above, let's make an array in TypeScript:

const numbers: number[] = [3, 14, 7, 11, 4];

How do we accessed? We can access the data inside the array (also known as element) with either usual for loop / foreach loop.

If we use usual for loop, we can use index (will be explained later, including why index starts from 0) of the number, by using [] bracket:

const numbers: number[] = [3, 14, 7, 11, 4];
for(let i = 0; i < numbers.length; i++) {
    console.log(numbers[i]);
}

If using foreach loop:

const numbers: number[] = [3, 14, 7, 11, 4];
for(const number of numbers) {
    console.log(number);
}

So, in order to square all numbers, here's the code:

const numbers: number[] = [3, 14, 7, 11, 4];
for(const number of numbers) {
    console.log(`${number} squared is ${number * number}`);
}

But, what do we mean by ordered and indexed?

Ordered means that elements in the array are stored in a particular order, so when you swap 2 elements (e.g. from [3, 14, 7, 11, 4] to [14, 3, 7, 11, 4]) then it is considered a different array. This also means that we can sort the elements inside the array, providing the data type supports comparison.

Indexed means that each element has an identifier in the array, which is the location of the data. Note that the location (or identifier, or index) is started from 0 because it follows computer memory's mapping which also starts from 0 (looking at you, R & Lua).

Set

The weakness of an array is that it is impossible to find an element in a quick way. The complexity of element search in array is O(n). If finding an element quickly (as fast as O(1) or O(n*log(n))) is crucial, then we have to use set.

Set is a data structure to store multiple data of same type. What's the difference with array? Unlike array, data in set are not ordered. That means you don't know whether this data is before / after other data. And, unlike array, you can actually find an element quickly. What is the usage then?

Let's take an example: we want to remember people we ever met. Now, if we store 8 names on array, everytime we want to check whether you ever visited them, we have to check all names to find a particular person we want to check.

const names: string[] = ["Lorem", "Delores", "Doha", "John", "Natasha", "Ria", "Salom", "Ari"];
const searchedName: string = "Ari";
for(const name of names) {
    if(searchedName == name) {
        console.log(`We have met with ${searchedName}`);
        break;
    }
}

That should not be a problem for 8 names, but how about 10 million names? That is a hell of a long process. But, we can speed up with set. Here's how to use set:

const names: Set<string> = new Set<string>(["Lorem", "Delores", "Doha", "John", "Natasha", "Ria", "Salom", "Ari"]);
const searchedName: string = "Ari";
if(names.has(searchedName)) {
    console.log(`We have met with ${searchedName}`);
}

To initialize set, we use keyword new. Set constructor receives either 0 (no parameter) / 1 parameter, which is the array that contains initial data. Then, all data in the array will be inserted into set automatically.

To check whether the data is in set / not, use has method (example shown above).

What if we want to add / delete data? Well, for add, we use add method, e.g.

const names: Set<string> = new Set<string>(["Lorem", "Delores", "Doha", "John", "Natasha", "Ria", "Salom", "Ari"]);
const newName: string = "Dejan";
names.add(newName);
console.log(names.has(newName)); // output: true

As for delete, we use delete method:

const names: Set<string> = new Set<string>(["Lorem", "Delores", "Doha", "John", "Natasha", "Ria", "Salom", "Ari"]);
const deleteName: string = "Lorem";
names.delete(deleteName);
console.log(names.has(deleteName)); // output: false

To clear all elements in set, use clear method:

const names: Set<string> = new Set<string>(["Lorem", "Delores", "Doha", "John", "Natasha", "Ria", "Salom", "Ari"]);
names.clear();
console.log(names.size); // output: 0

One thing about set is that set can only store unique data. For our example, we cannot have duplicate names in set. Whereas array, we can have duplicate names (or numbers, or any data). For comparison:

const namesInArray: string[] = ["Lorem", "Delores", "Doha", "John", "Natasha", "Ria", "Salom", "Ari", "Doha"];
const namesInSet: Set<string> = new Set<string>(namesInArray);

console.log(namesInArray.length); // output: 9
console.log(namesInSet.size); // output: 8

The other thing about set is that the data type must implement equality. Equality means that objects / data of that data type can be determined whether they're equal or not. More information here.

For more information about set, check here.

Map

Similar to Set, Map is used to store unique data. But, unlike Set, Map has the ability to store additional data that is related to the unique data (known as key).

E.g. your account number is 5 and you have $ 1000 in your bank account, while my account number is 11 and I have $ 500 in my account. How to store the data in map?

const account: Map<number, number> = new Map<number, number>();
account.set(5, 1000);
account.set(11, 500);

console.log(account.has(5)); // expected output: true
console.log(account.get(5)); // expected output: 1000
console.log(account.has(11)); // expected output: true
console.log(account.get(11)); // expected output: 500

Read more about TypeScript's Map here.

Condition

So, let's say we want to print Odd when a number is odd, or print Even when it's even. Now, how do we do that? Let's talk about if syntax.

If is a syntax in TypeScript (and common in other programming languages) to decide what to do for a certain condition. Let's take an example case: print Odd only when a number is odd. How do we do that? Let's see at this code.

const a: number = 5;
if(a % 2 == 0) {
    console.log("Odd");
}

First, we declare the variable a with value 5. And then, we use the if syntax, where it receives a condition (remember boolean from above). Write the condition inside the parentheses, write the code you want to execute inside the curly brace block. Everytime the condition is fulfilled, code inside the block will be executed.

Note: The condition for checking whether a number is even / odd is using remainder (%) operator.

Let's try change the value to 4. Now, nothing is printed. The code block inside curly brace is not executed because the condition is not fulfilled.

Let's change back the value to 5. Now, we're going to move the condition into a separate variable, like this:

const a: number = 5;
const c: boolean = a % 2 == 0;
if(c) {
    console.log("Odd");
}

Try executing this, and Odd word will be printed. You can even move it into a separate function (what is function, it will be explained later), like this:

function checkCondition(a: number): boolean {
    return a % 2 == 0;
}

const a: number = 5;
if(checkCondition(a)) {
    console.log("Odd");
}

What happens if we want to do something if a condition is not fulfilled? Example: print Even when a given number is even, otherwise print Odd. The answer is to use else block. Here's how to do it:

const a: number = 2;
if(a % 2 == 0) {
    console.log("Even");
} else {
    console.log("Odd");
}

Try changing the value of a variable from even to odd, and see whether the output changes / not.

The if-else syntax has a variation to handle more than 2 "branches" (we call each program flow as "branch"), which is if-else if syntax, on which we can look into here.

There's an alternative to if-else, which is switch-case syntax. Learn more here.

Function

Let's say you have a part of code that's not necessary executed repeated times, but executed in 1 / more branches. Or, we just want to separate a logic from the main program, just to ease our development process. That's why we use function keyword.

One example is from above's code, where we separate a code to check whether a number is even or odd into a new function (so that piece of code is reusable).

function checkCondition(a: number): boolean {
    return a % 2 == 0;
}

const a: number = 5;
if(checkCondition(a)) {
    console.log("Odd");
}

A function doesn't have to return value. Maybe a function is intended only to print, that's also valid. Example:

function checkCondition(a: number): boolean {
    return a % 2 == 0;
}

function printNumber(a: number) {
    if(checkCondition(a)) {
        console.log(`condition is fulfilled for ${a}`);
    } else {
        console.log(`condition is not fulfilled for ${a}`);
    }
}

for(let i = 0; i <= 100; i++) {
    printNumber(i);
}

Learn more about function here and here.

Recursive Function

A function can call other functions, including itself. This is called recursion. If recursion happened, the function will not be finished after it finished calling itself.

Then, you must think: "Wait. When does the function ends?" That's why we need a scenario where recursion will not happen. Such scenario is called base case. If there's no base case, then the program will return stack overflow error.

The easiest example of recursive function is Fibonacci formula. Try this code in TypeScript Playground:

function fibonacci(n: number): number {
    if(n == 0 || n == 1) { // this is base case
        return n;
    }

    return fibonacci(n - 1) + fibonacci(n - 2);
}

const number = 6;
console.log(fibonacci(number));

Web Application

Web application is an application that is accessed by, well, web browser. Easy right? But why web app, you might ask. There are several advantages*:

  1. HTTP (you will learn later) is lightweight and connectionless. Lightweight means it doesn't take too much resources, while connectionless means each data unit is individually taken care of, instead of pre-arranged before the connection is made.
  2. Every web user already has a browser installed on their computer.
  3. Web interfaces use standard navigational and input controls that are immediately familiar to users, avoiding the need to learn how each individual application functions.
  4. The core technologies and languages used to develop web applications are relatively simple.

* Stuttard, D., & Pinto, M. (2011). The web application hacker's handbook: Finding and exploiting security flaws. John Wiley & Sons.

Basic Concepts

Protocols

Wait, what? Yes, you read it correctly. Internet is a network, and like other things, it needs a protocol. Network protocol is, let's just say, a set of rules that determined how data are sent between computers*.

* Source: What Is a Network Protocol, and How Does It Work?

HTTP
TCP
UDP
WebSockets
Implementation

Event Loop

Async-Await

NOTE: read / watch about event loop first

Tech Stack

Tech stack is a set of programs (either programming languages, SDKs, libraries, or services) that complement your application. E.g. your web application needs database, so you pick MariaDB as your database. Or, your application needs a message broker, so you pick Rabbit MQ as your message broker.

NOTE: Tech stack will change in the future. Just pick what you need. Don't let anyone "convince" you that a particular tech stack is the best.

JavaScript Runtime Environment

Wait, what is this?

There is a good explanation about JavaScript Runtime Environment here.

TL;DR: The environment where your JavaScript code will be executed.

NOTE: The runtime explained in the Medium article is Google Chrome's V8 engine, which is used to run JavaScript code in browser. However, depending on your application, you might want to run JavaScript outside of browser. Node.js and Deno provide such feature. However, the concepts explained (event-loop, etc.) are similar, if not same with Node.js.

Node.js
Deno

Server-Client Architecture

When we access internet, we (through web browser) actually ask for data. Then, the data is shown either as web page (if accessed through web browser), or processed as API (in other programs). The party who requested data (and then later show / process the data) is called client, while the party who has & server client's request for data is called server.

In web-based (or internet-based) software development, there are 2 roles that need to exist to fulfill client-server architecture:

  1. Frontend: programmer's role to develop and maintain the view of the web / application. Frontend programmer takes care of the view & interaction to end-user.
  2. Backend: programmer's role to develop and maintain the server. Backend programmer takes care of the server, database, and other behind-the-scenes infrastructure.
Backend

Frameworks by language:

  • TypeScript
    • Express
    • Restify
  • Rust
    • Actix
    • Rocket
  • Java
    • Spring Boot
    • Play Framework
  • Go
    • Gin
    • Echo
Database

Relational:

  • PostgreSQL
  • MySQL
  • MariaDB

Document-oriented:

  • MongoDB
  • Elasticsearch
Message Broker

AMQP protocol (general purpose):

  • Rabbit MQ

Other general purpose:

  • Kafka

MQTT protocol (IoT and embedded):

  • Mosquitto
Container
Frontend

FE frameworks:

  • React
  • Vue
  • Angular

UI Kit:

  • Material
  • Ant Design
  • Bootstrap
  • Tailwind

Peer-to-Peer Communication

As explained, server receives a request and sends data, while client requests and receives data. But, there are cases where both parties ask, send, and receive data, simultaneously. The easiest example is chat application and Torrent downloading mechanism. There's no single source of data, instead data is owned by all parties. That's called peer-to-peer architecture.

Socket.IO
WebRTC

How to Develop

Methodology

Software development methodologies:

  • Waterfall
  • Agile
    • Scrum
    • Xtreme Programming

General steps (top-down approach):

  1. Requirements are documented in Product Requirement Document (PRD).
  2. Convert the PRD to UI design.
  3. Convert the PRD and UI design to tech design documentation. Tech design documentation must include at minimum:
    • Database model.
    • Tickets (with clear scope).
    • UML diagram, to understand relations between objects (only if using object-oriented approach).
  4. Start develop.

Tools That Maybe Needed

UI/UX Design

Project Management

Documentation

REST API Documentation

CI/CD

What is CI/CD
Tools

Tips and Explanations

Git

About Type Error

To answer this question, first we have to understand data type. In programming language, a data (or more commonly known as variable) has a certain structure / characteristic. For example, a string variable can hold a number of characters, while a number can hold, well, number. A number variable can be "used" the way we usually use numbers: by adding (+), subtract (-), multiply (*), or divide(/).

So far, so good. But, how do computers know whether we have a number / string? The answer is: they don't know and they don't care. Why? Because that's not what computers are concerned about. Computers are concerned about these things:

  1. how many bytes of memory will be allocated for the data, along with its position in the memory.
  2. what can computers do with that data? For example, a number variable can be used to addition and subtraction. Also, what happens if 2 data with different type are operated as if they're the same type? This needs to be taken into account.

Wait, if they don't care, why should we care? Because it is our data that we want to store. So, programmer decide the data type, while programming language later help to convert the data from given type into byte format that computer machine can save.

Some programming languages (e.g. JavaScript, Python, Ruby) do not check data type, but rather during program's execution, they will "throw" you some errors if there is indeed an incompatibility error between the expected data type and actual data type being given.

Other programming languages (e.g. C++, Java, Rust, and TypeScript) will throw error during compile time (the time used for compiler to "convert" the code to machine language) if the data type violation is found.

There is a good article about statically-typed here.

Web Game

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment