Databeheer met TypeORM

Databeheer met TypeORM

INTRODUCTIE

TypeORM is an ORM ( Object Relational Mapper) that can run in NodeJS and can be used with JavaScript (ES5, ES6, ES7, ES8). Its goal is to help you to develop any kind of application that uses databases - from small applications with a few tables to large scale enterprise applications with multiple databases.

Installatie

Installeer typeorm

npm install typeorm

Installeer sqlite3

npm install sqlite3

Installeer body-parser

npm install body-parser

Voorbereiding

  • Om data heen en weer naar onze applicatie te sturen, hebben we de body-parser nodig.
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
  • Vergeet niet om de body-parser te importeren
import bodyParser from "body-parser";

Voorbereiding

  • Maak een bestand DataSource.js aan in de ./lib-folder waar we de connectie configureren met de database
import { DataSource } from "typeorm";

const DS = new DataSource({
  type: process.env.DATABASE_TYPE,
  database: process.env.DATABASE_NAME,
  synchronize: true,
});

export default DS;

Voorbereiding

  • Importeer die file in app.js
import DataSource from "./lib/DataSource.js";
DataSource.initialize()
  .then(() => {
    // start the server
    app.listen(process.env.PORT, () => {
      console.log(
        `Application is running on http://localhost:${process.env.PORT}/.`
      );
    });
  })
  .catch(function (error) {
    console.log("Error: ", error);
  });
  • En initialiseer de database-verbinding!

Voorbereiding

  •  Let op: we gebruiken in bovenstaande code variabelen uit een .env bestand 
PORT=3000
DATABASE_TYPE=sqlite
DATABASE_NAME="typeorm-demo.sqlite3"

Voorbereiding

  • Wanneer alles lukt dan zou je nu in de root van je project een typeorm-demo bestand moeten hebben staan.

DBBrowser

DBBrowser

  • Met deze tool kan je het typeorm-demo bestand openen en de data bekijken/wijzigen.

VS code

  • Om tijdens de lessen webservices te consumeren, gebruiken we de vscode-extension Thunder Client.

Databeheer met TypeORM

EEN VARIABELE NAVIGATIE

An entity is an object that exists. It doesn't have to do anything; it just has to exist. In database administration, an entity can be a single thing, person, place, or object.

Model

  • Maak een nieuwe entity aan in de models folder: NavigationItem
  • Je NavigationItem bevat een id, een url en een text
  • TypeORM maakt voor elke entity:
    • Een tabel (navigation_items)
    • Eén of meerdere velden (url en text)
    • Eén uniek veld dat auto incrementeel is, de primary key (het veld id)
    • Al dan niet een tussentabel voor relations

NavigationItem

import typeorm from "typeorm";
const { EntitySchema } = typeorm;

export default new EntitySchema({
  name: "NavigationItem",
  tableName: "navigation_items",
  columns: {
    id: {
      primary: true,
      type: "int",
      generated: true,
    },
    url: {
      type: "varchar",
    },
    text: {
      type: "varchar",
    },
  },
});

NavigationItem

  • Elke tabel in TypeORM heet een repository
  • Je kan in een repository data:
    • Aanmaken (Create)
    • Opvragen (Read)
    • Wijzigen (Update)
    • Verwijderen (Delete)
  • Op een SQL database voer je queries uit om CRUD-acties uit te voeren.

NavigationItem

Voorbeeld SQL-syntax:

SELECT navigation_items.id, navigation_items.url, navigation_items.text
FROM navigation_items
WHERE navigation_items.url="https://www.google.be"

Wij zullen geen SQL schrijven tijdens de lessen, achterliggend zal TypeORM jouw verzoeken omzetten naar SQL zodat de dev-complexiteit van onze applicatie aanzienlijk lager ligt.

NavigationItem

  • We voegen via de SQL Browser wat data toe:
    1. Open de database
    2. Rechterklik op navigation_items en blader door de gegevens
    3. Vul de velden url en text in
      • url: https://www.arteveldehs.be
      • text: Arteveldehogeschool
    4. Bewaar de wijzigingen

NavigationItem

  • We halen de wijzigingen op in onze home-controller
    1. Haal de open database connectie op
       
    2. Haal de repository NavigationItem op
       
    3. Haal alle data op uit de database
      • Het resultaat is een Promise, dus je wacht tot de database is opgehaald
import DataSource from "../lib/DataSource.js";
const navigationItemRepository = DataSource.getRepository("NavigationItem");
const menuItems = await navigationItemRepository.find();

NavigationItem

  • Vervang de menuitems met de data die we net hebben opgehaald, de pagina wordt nu uitgerenderd met dynamische data
  • Hieronder een volledige codesnippet van de home-controller
// import typeorm
import DataSource from "../lib/DataSource.js";

export const home = async (req, res) => {
  // get reposito
  const navigationItemRepository = DataSource.getRepository("NavigationItem");

  // fetch the menu items
  const menuItems = await navigationItemRepository.find();

  res.render("home", {
    menuItems
  });
};

Databeheer met TypeORM

HTTP Methods

The primary or most-commonly-used HTTP verbs (or methods, as they are properly called) are POST, GET, PUT, and DELETE. These correspond to create, read, update, and delete (or CRUD) operations, respectively.

HTTP Methods

  • Met HTTP methods sturen we een verzoek van client naar server, altijd met een bepaald doel
  • GET - Het opvragen van data
  • POST - Het sturen van data van client -> server
  • PUT - Het aanpassen van data
  • DELETE - Het verwijderen van data

Een overzicht van andere HTTP methods kan je hier vinden.

Databeheer met TypeORM

REST API

Een REST API (Representational State Transfer Application Program Interface) is een architectuurstijl waarmee software kan communiceren met andere software via een netwerk of op hetzelfde apparaat. Doorgaans gebruiken ontwikkelaars REST-API's om webservices te bouwen.

REST API

  • Wij maken een webservice en communiceren met HTTP-methods om CRUD-acties uit te voeren.
  • We kunnen CRUD-bewerkingen uitvoeren om
    • Interesse's op te halen (GET)
    • Interesse's toe te voegen (POST)
    • Interesse's te verwijderen (DELETE)
    • Interesse's aan te passen (PUT) 

Interests Entity

import typeorm from "typeorm";

const { EntitySchema } = typeorm;

export default new EntitySchema({
  name: "Interest",
  tableName: "interests",
  columns: {
    id: {
      primary: true,
      type: "int",
      generated: true,
    },
    name: {
      type: "varchar",
    }
  },
});

API Controller

  • Om de REST-api te kunnen aanspreken, maken we nieuwe controllers aan. 
  • Maak een folder api in de controllers folder aan
  • Maak in de api folder een bestand interest.js
  • Exporteer 4 "lege" functies:
    1. getInterests
    2. updateInterest
    3. deleteInterest
    4. postInterest

API Controller

API Routes

  • Maak nieuwe routes in je app.js bestand aan. In een REST-api noemen we dit endpoints. Ofwel, de URL die we gebruiken om een HTTP-method uit te voeren.

API Routes

  • Vergeet niet om de functies die je aanspreekt te importeren vanaf je api controller.
import { 
    postInterest, 
    deleteInterest, 
    getInterests, 
    updateInterest 
} from "./controllers/api/interest.js";

postInterest

  • Om data toe te voegen aan de typeorm repository, gebruiken we de functie save()
// insert the interests
const insertedInterest = await interestRepository.save(req.body);
  • Van deze functie krijgen we de nieuw ingevoegde data terug.
  • LET OP: ook deze functie geeft een Promise terug waarop we moeten wachten.

getInterest

  • Om data op te halen gebruiken we
    • find() - om alle records op te vragen die voldoen aan de zoekcriteria
    • findOne() - om slechts één record op te vragen die voldoet aan de zoekcriteria.
  • Zoekcriteria geven we mee via de where property in het optionele object:

 

const interest = await interestRepository.findOne({
  where: { name: req.body.name }
});

updateInterest

  • Om data aan te passen en te bewaren, gebruiken we de functie save(). (Opmerking: we gebruiken hier de verkorte findOneBy() functie)
// check if the name already exists
const interest = await interestRepository.findOneBy({
  id: req.body.id
});

// update the interest
await interestRepository.save({ ...interest, ...req.body });
  • Via de spread syntax kunnen we een nieuw object maken van bestaande data en nieuwe data.

deleteInterest

  • Om data te verwijderen, gebruiken we de functie remove()
// check if the id exists
const interest = await interestRepository.findOneBy({ id: 1 });

// remove the interest
await interestRepository.remove({ id });
  • Je kan geen body object meegeven aan een delete HTTP method, je stuurt een id mee via de parameters van je endpoint

Oefening

  • Je kan nu een User entity maken met
    • id: de primary key van een user
    • firstname: de voornaam van een gebruiker 
    • lastname: de familie naam van een gebruiker
  • Je voorziet in je api volgende endpoints:
    • GET - /user - controller getUsers()
    • DELETE - /user/:id - controller deleteUser()
    • POST - /user - controller postUser()
    • PUT - /user - controller updateUser()

Databeheer met TypeORM

RELATIONS

In relational databases, a relationship exists between two tables when one of them has a foreign key that references the primary key of the other table. This single fact allows relational databases to split and store data in different tables, yet still link the disparate data items together.

One-To-One

  • Een verwijzing uit de ene tabel heeft slechts één verwijzing naar een record in een andere tabel.
  • Maak een UserMeta entity met daarin extra informatie over een user: address, zipcode en city.
  • We maken de relatie langs twee kanten kenbaar, dat betekent dus zowel in de User als in de UserMeta entity

One-To-One

import typeorm from "typeorm";

const { EntitySchema } = typeorm;

export default new EntitySchema({
  name: "UserMeta",
  tableName: "user_meta",
  columns: {
    id: {
      primary: true,
      type: "int",
      generated: true,
    },
    address: {
      type: "varchar",
    },
    zipCode: {
      type: "varchar",
    },
    city: {
      type: "varchar"
    }
  }
});

Entity: UserMeta

One-To-One

  • Voeg de relatie toe aan de UserMeta entity.
import typeorm from "typeorm";
const { EntitySchema } = typeorm;

export default new EntitySchema({
  name: "UserMeta",
  tableName: "user_meta",
  columns: {
    // ...
  },
  relations: {
    user: {
      target: "User",
      type: "one-to-one",
      joinColumn: {
        name: "user_id",
      },
      onDelete: "CASCADE",
    },
  },
});

Entity: UserMeta

One-To-One

  • Met joinColumn geven we TypeORM de opdracht om een verwijs-kolom toe te voegen. Deze zal de id bevatten van het gerelateerde User-record.
  • Door onDelete op "CASCADE" te zetten zal TypeORM automatisch dit gerelateerde record wissen als de user-record gewist wordt.

Entity: UserMeta

One-To-One

  • De relatie beschrijven we ook in de user-entity
import typeorm from "typeorm";
const { EntitySchema } = typeorm;

export default new EntitySchema({
  name: "User",
  tableName: "users",
  columns: {
    // ...
  },
  relations: {
    meta: {
      target: "UserMeta",
      type: "one-to-one",
      inverseSide: "user",
      cascade: true,
    },
  },
});

Entity: User

One-To-One

  • Door cascade op true te zetten kan TypeORM automatisch data toevoegen aan de andere tabel.
  • De inverseSide property heeft TypeORM nodig om de data van de ene kant aan de andere kant te koppelen (de value "user" is gelijk aan de relation-property van de andere zijde).

 

We kunnen nu users ophalen, inclusief de relatie via

const users = await userRepository.find({relations: ["meta"]});

Entity: User

One-To-One

  • Bewaren kunnen we nu door onze meta data mee te geven bij het bewaren.
const insertedUser = await userRepository.save({
  ...req.body,      
  meta: {
    address: "Industrieweg 232",
    zipCode: "9050",
    city: "Mariakerke"
  }
});

One-To-Many/Many-To-One

  • In deze relatie zal entity A meerdere instanties bevatten van entity B.
  • Een gebruiker (User) kan meerdere huisdieren (Pet) bevatten.
  • Deze relatie moet aan beide kanten kenbaar worden gemaakt.

One-To-Many/Many-To-One

  • Maak een Pet entity
import typeorm from "typeorm";
const { EntitySchema } = typeorm;

export default new EntitySchema({
  name: "Pet",
  tableName: "pets",
  columns: {
    id: {
      primary: true,
      type: "int",
      generated: true,
    },
    animal: {
      type: "varchar",
    },
    name: {
      type: "varchar",
    },
    age: {
      type: "int",
    },
  },
  relations: {},
});

One-To-Many/Many-To-One

  • Voeg aan de User verwijzing naar Pet toe.
    Er is één gebruiker en meerdere huisdieren
    • one-to-many
[...]
relations: {    
   meta: {
      target: "UserMeta",
      type: "one-to-one",
      inverseSide: "user",
      cascade: true,
   },
   pets: {
      target: "Pet",
      type: "one-to-many",
      inverseSide: "owner",
      cascade: true,
  }
}
[...]

One-To-Many/Many-To-One

  • Voeg aan Pet een verwijzing naar User toe. Er zijn meerdere huisdieren met één gebruiker (eigenaar)  
    • many-to-one
[...]
relations: {
  owner: {
      target: "User",
      type: "many-to-one",
      inverseSide: "pets",
      joinColumn: {
        name: "owner_id",
      },
      onDelete: "CASCADE",
    },
}
[...]

One-To-Many/Many-To-One

  • Nu kunnen we een zoekopdracht uitvoeren van beide kanten.
await userRepository.find({ relations: ["pets"] });
&
await petRepository.find({ relations: ["owner"] });

One-To-Many/Many-To-One

  • Ook bewaren is nu kinderspel
await userRepository.save({
  ...req.body,  
  pets: [
    { animal: "dog", name: "felix", age: 5 },
    { animal: "cat", name: "miss puss", age: 1 }
  ]
});

Many-To-Many

  • Meerdere referenties kunnen meerdere keren voorkomen... OFWEL meerdere primary keys verwijzen naar meerdere referenties in een andere tabel.

 

  • Meerdere users kunnen meerdere interesses hebben
    en...
    een bepaalde interesse kan voor meerdere users gelden

Many-To-Many

  • We passen het entity schema van een User aan
import typeorm from "typeorm";
const { EntitySchema } = typeorm;

export default new EntitySchema({
  name: "User",
  tableName: "users",
  columns: {
    // ...
  },
  relations: {
    // ...,
    interests: {
      target: "Interest",
      type: "many-to-many",
      joinTable: {
        name: "users_interests",
      },
      cascade: true
    }
  }
});

Many-To-Many

  • We passen nu postUser() aan, ter demonstratie, met alle interesse's uit de interests repository.
export const postUser = async (req, res, next) => {
  try {
    // get the repository
    const userRepository = DataSource.getRepository('User');
    const interestRepository = DataSource.getRepository('Interest');

    // FOR DEMO - insert the user and fetch all the interests
    const insertedUser = await userRepository.save({
      ...req.body,
      interests: await interestRepository.find()
    });

    // send a status code
    res.status(200).json({ status: `Inserted user with id ${insertedUser.id}.` });
  } catch(e) {
    next(e.message)
  }
}

Many-To-Many

  • Willen we nu de relaties ook ophalen met find(), dan moeten we dit ook hier expliciet meegeven. Bijv. getUsers()
export const getUsers = async (req, res, next) => {
  try {
    // get the repository
    const userRepository = DataSource.getRepository('User');

    // get the interests and return them with status code 200
    res.status(200).json(await userRepository.find({ relations: ["interests"] }));
  } catch(e) {
    next(e.message);
  }
}

Databeheer met TypeORM

By timdpaep

Databeheer met TypeORM

  • 488