Implement LSTM Network using Python with TensorFlow and Keras for prediction and classification

A powerful type of Recurrent Neural Networks – Long Short-Term Memory (LSTM) is not only transmitting output information to the next time step, but they are also storing and transmitting the state of the so-called LSTM cell. This cell contains four neural networks – gates that determine which information is stored in the cell state and pushed to output. As a result, the output of the network at a one-time step is dependent on n previous time steps rather than just the previous time step.

In this article, we will look at two similar language modeling problems and see how they can be solved using two different APIs. To begin, we will build a network that can predict words based on the provided text, and we will use TensorFlow for this. In the second implementation, we will use Keras to classify reviews from the IMDB dataset.

Implement a Model with Tensorflow

The DataHandler class in DataHandler.py will be used. This class serves two functions: it loads data from a file and assigns a number to each symbol. The code is as follows:

import numpy as np
import collections

class DataHandler:
    def read_data(self, fname):
        with open(fname) as f:
            content = f.readlines()
        content = [x.strip() for x in content]
        content = [content[i].split() for i in range(len(content))]
        content = np.array(content)
        content = np.reshape(content, [-1, ])
        return content
    
    def build_datasets(self, words):
        count = collections.Counter(words).most_common()
        dictionary = dict()
        for word, _ in count:
            dictionary[word] = len(dictionary)
        reverse_dictionary = dict(zip(dictionary.values(), dictionary.keys()))
        return dictionary, reverse_dictionary

We will create a new class RNNGenerator in rnn_generator.py that can generate an LSTM network based on the parameters passed in.

import tensorflow as tf
from tensorflow.contrib import rnn

class RNNGenerator:
    def create_LSTM(self, inputs, weights, biases, seq_size, num_units):
        # Reshape input to [1, sequence_size] and split it into sequences
        inputs = tf.reshape(inputs, [–1, seq_size])
        inputs = tf.split(inputs, seq_size, 1)
    
        # LSTM with 2 layers
        rnn_model = rnn.MultiRNNCell([rnn.BasicLSTMCell(num_units),rnn.BasicLSTMCell(num_units)])
    
        # Generate prediction
        outputs, states = rnn.static_rnn(rnn_model, inputs, dtype=tf.float32)
    
        return tf.matmul(outputs[–1], weights['out']) + biases['out']

TensorFlow itself, as well as the RNN class from tensorflow.contrib, were imported. We will use our LSTM Network, which is a subtype of RNNs, to create our model. First, we reshaped our input and then divided it into three-symbol sequences. The model was then created.

Using the BasicLSTMCell method, we created two LSTM layers. The parameter num units specify the number of units in each of these layers. Aside from that, we use MultiRNNCell to integrate these two layers into a single network. The static RNN method was then used to build the network and generate predictions.

Finally, we will employ the SessionRunner class in session_runner.py. This class contains the environment used to run and evaluate our model. Here’s how the code works:

import tensorflow as tf
import random
import numpy as np

class SessionRunner():
    training_iters = 50000
            
    def __init__(self, optimizer, accuracy, cost, lstm, initilizer, writer):
        self.optimizer = optimizer
        self.accuracy = accuracy
        self.cost = cost
        self.lstm = lstm
        self.initilizer = initilizer
        self.writer = writer
    
    def run_session(self, x, y, n_input, dictionary, reverse_dictionary, training_data):
        
        with tf.Session() as session:
            session.run(self.initilizer)
            step = 0
            offset = random.randint(0, n_input + 1)
            acc_total = 0
        
            self.writer.add_graph(session.graph)
        
            while step < self.training_iters: if offset > (len(training_data) - n_input - 1):
                    offset = random.randint(0, n_input+1)
        
                sym_in_keys = [ [dictionary[ str(training_data[i])]] for i in range(offset, offset+n_input) ]
                sym_in_keys = np.reshape(np.array(sym_in_keys), [-1, n_input, 1])
        
                sym_out_onehot = np.zeros([len(dictionary)], dtype=float)
                sym_out_onehot[dictionary[str(training_data[offset+n_input])]] = 1.0
                sym_out_onehot = np.reshape(sym_out_onehot,[1,-1])
        
                _, acc, loss, onehot_pred = session.run([self.optimizer, self.accuracy, self.cost, self.lstm], feed_dict={x: sym_in_keys, y: sym_out_onehot})
                acc_total += acc
                
                if (step + 1) % 1000 == 0:
                    print("Iteration = " + str(step + 1) + ", Average Accuracy= " + "{:.2f}%".format(100*acc_total/1000))
                    acc_total = 0
                step += 1
                offset += (n_input+1)

Our model is being run through 50000 iterations. We injected the model, optimizer, loss function, and other information into the constructor so that the class could use it. Naturally, the first step is to slice up the data in the provided dictionary and generate encoded outputs. In addition, we are introducing random sequences into the model to avoid overfitting. The offset variable handles this. Finally, we’ll run the session to determine accuracy. Don’t be confused by the final if statement in the code; it’s just for show (at every 1000 iterations present the average accuracy).

Our main script main.py combines all of this into one, as shown below:

import tensorflow as tf
from DataHandler import DataHandler
from RNN_generator import RNNGenerator
from SessionRunner import SessionRunner

log_path = '/output/tensorflow/'
writer = tf.summary.FileWriter(log_path)

# Load and prepare data
data_handler = DataHandler()

training_data =  data_handler.read_data('meditations.txt')

dictionary, reverse_dictionary = data_handler.build_datasets(training_data)

# TensorFlow Graph input
n_input = 3
n_units = 512

x = tf.placeholder("float", [None, n_input, 1])
y = tf.placeholder("float", [None, len(dictionary)])

# RNN output weights and biases
weights = {
    'out': tf.Variable(tf.random_normal([n_units, len(dictionary)]))
}
biases = {
    'out': tf.Variable(tf.random_normal([len(dictionary)]))
}

rnn_generator = RNNGenerator()
lstm = rnn_generator.create_LSTM(x, weights, biases, n_input, n_units)

# Loss and optimizer
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=lstm, labels=y))
optimizer = tf.train.RMSPropOptimizer(learning_rate=0.001).minimize(cost)

# Model evaluation
correct_pred = tf.equal(tf.argmax(lstm,1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

# Initializing the variables
initilizer = tf.global_variables_initializer()

session_runner = SessionRunner(optimizer, accuracy, cost, lstm, initilizer, writer)
session_runner.run_session(x, y, n_input, dictionary, reverse_dictionary, training_data)

The content of meditations.txt is “In a sense, people are our proper occupation. Our job is to do them good and put up with them. But when they obstruct our proper tasks, they become irrelevant to us—like sun, wind, and animals. Our actions may be impeded by them, but there can be no impeding our intentions or our dispositions. Because we can accommodate and adapt. The mind adapts and converts to its own purposes the obstacle to our acting. The impediment to action advances action. What stands in the way becomes the way .”

We run the code and get accuracy above 95% with iteration 50000

Implement a model with Keras

This TensorFlow example was straightforward and simple. We used a small amount of data, and the network learned this fairly quickly. What if we have a more complicated issue? Assume we want to categorize the sentiment of each movie review on a website. Fortunately, there is already a dataset dedicated to this issue – The Large Movie Review Dataset (often referred to as the IMDB dataset).

Stanford researchers collected this dataset in 2011. It includes 25000 movie reviews (both positive and negative) for training and the same number of reviews for testing. Our goal is to build a network that can determine which reviews are positive and which are negative.

The power of Keras is that it abstracts a lot of what we had to worry about while using TensorFlow. However, it provides us with less flexibility. Of course, everything has a cost. So, let’s begin by importing the necessary classes and libraries.

There is a slight difference in imports between examples where we used standard ANN and examples where we used Convolutional Neural Network. We brought in Sequential, Dense, and Dropout. Nonetheless, we can see a couple of new imports. We used Embedding and LSTM from keras.layers. As you might expect, LSTM is used to create LSTM layers in networks. In contrast, embedding is used to provide a dense representation of words.

This is an interesting technique for mapping each movie review into a real vector domain. Words are encoded as real-valued vectors in a high-dimensional space, with similarity in meaning corresponding to closeness in the vector space.

We are loading the top 1000 words dataset. Following that, we must divide the dataset and generate and pad sequences. This is accomplished by utilizing sequence from keras.preprocessing

from keras.preprocessing import sequence 
from keras.models import Sequential 
from keras.layers import Dense, Dropout, Embedding, LSTM 
from keras.datasets import imdb 

num_words = 1000 
(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=num_words) 

X_train = sequence.pad_sequences(X_train, maxlen=200) 
X_test = sequence.pad_sequences(X_test, maxlen=200)

# Define network architecture and compile 
model = Sequential() 
model.add(Embedding(num_words, 50, input_length=200)) 
model.add(Dropout(0.2)) 
model.add(LSTM(100, dropout=0.2, recurrent_dropout=0.2)) 
model.add(Dense(250, activation='relu')) 
model.add(Dropout(0.2)) 
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) 


model.fit(X_train, y_train, batch_size=64, epochs=10) 

print('\nAccuracy: {}'. format(model.evaluate(X_test, y_test)[1]))

We used the number 200 in the padding to indicate that our sequences will be 200 words long. This is how the training input data looks:

Sequential is used for model composition, as we have seen in previous articles. The first layer added to it is Embedding, which we discussed in the previous chapter. We added one LSTM layer after the word embedding was completed. Finally, because this is a classification problem, we add a dense layer with a sigmoid function to determine whether the review was good or bad. Finally, the model is compiled using binary cross-entropy and the Adam optimizer.

We got an accuracy of 85.05%

When developing LSTM networks, we observed two approaches. Both approaches dealt with simple problems and employed a different API. As can be seen, TensorFlow is more detailed and flexible; however, you must take care of a lot more details than when using Keras. Keras is simpler and easier to use, but it lacks the flexibility and possibilities that pure TensorFlow provides. Both of these examples produced acceptable results, but they could have been better. Especially in the second example, where we typically use a combination of CNN and RNN to improve accuracy, but that is a topic for another article.

Training and deploying machine learning models with TensorFlow.js JavaScript Library

Deep learning and machine learning algorithms can be executed on Javascript with TensorFlow.js. In the article, we will introduce how to define, train and run your machine learning models using the API of TensorFlow.js.

With a few lines of Javascript, developers can implement pre-trained models for complex tasks such as visual recognition, generating music, or human poses detection. Node.js allows TensorFlow.js can be used in backend Javascript applications rather than use Python.

TensorFlow Javascript

TensorFlow is a popular open-source software library for machine learning applications. Many neural networks and other deep learning algorithms use the TensorFlow library, which was originally a Python library released by Google in November 2015. TensorFlow can use either CPU or GPU-based computation for training and evaluating machine learning models. The library was originally developed to operate on high-performance servers with GPUs.

In May 2017, Tensorflow Lite, a lightweight version of the library for mobile and embedded devices, was released. This was accompanied by MobileNet, a new series of pre-trained deep learning models for vision recognition tasks. MobileNet models were created to perform well in resource-constrained environments such as mobile devices.

TensorFlow.js follow TensorFlow Lite, was announced in March 2018. This version of the library was built on an earlier project called deeplearn.js and was designed to run in the browser. WebGL allows for GPU access to the library. To train, load, and run models, developers use a JavaScript API.

TensorFlow.js is a JavaScript library that can be used to train and deploy machine learning models in the browser and Node.js. TensorFlow.js was recently extended to run on Node.js by using the tfjs-node extension library.

Are you familiar with concepts such as Tensors, Layers, Optimizers, and Loss Functions (or willing to learn them)? TensorFlow.js is a JavaScript library that provides flexible building blocks for neural network programming.

Learn how to use TensorFlow.js code in the browser or Node.js to get started.

Get Setup

Importing Existing Models Into TensorFlow.js

The TensorFlow.js library can be used to run existing TensorFlow and Keras models. Models must be converted to a new format using this tool before they can be executed. Github hosts pre-trained and converted models for image classification, pose detection, and k-nearest neighbors are available on Github.

Learn how to convert pre-trained Python models to TensorFlow.js here.

Learn by looking at existing TensorFlow.js code. tfjs-examples provide small code examples that use TensorFlow.js to implement various ML tasks. See it on GitHub

Loading TensorFlow Libraries

TensorFlow’s JavaScript API is accessible via the core library. Node.js extension modules do not expose any additional APIs.

const tf = require('@tensorflow/tfjs')
// Load the binding (CPU computation)
require('@tensorflow/tfjs-node')
// Or load the binding (GPU computation)
require('@tensorflow/tfjs-node-gpu')

Loading TensorFlow Models

TensorFlow.js includes an NPM library (tfjs-models) to make it easier to load pre-trained and converted models for image classification, pose detection, and k-nearest neighbors.

The MobileNet image classification model is a deep neural network that has been trained to recognize 1000 different classes.

The following example code is used to load the model in the project’s README.

import * as mobilenet from '@tensorflow-models/mobilenet';

// Load the model.
const model = await mobilenet.load();

One of the first issues I ran into was that this does not work on Node.js.

Error: browserHTTPRequest is not supported outside the web browser.

The mobilenet library is a wrapper around the underlying tf.Model class, according to the source code. When the load() method is invoked, the correct model files are automatically downloaded from an external HTTP address and the TensorFlow model is instantiated.

The Node.js extension does not yet support HTTP requests to retrieve models dynamically. Models must instead be manually loaded from the filesystem.

After reading the library’s source code, I was able to devise a workaround…

Loading Models From a Filesystem

If the MobileNet class is created manually, rather than calling the module’s load method, the auto-generated path variable containing the model’s HTTP address can be overwritten with a local filesystem path. After that, calling the load method on the class instance will invoke the filesystem loader class rather than the browser-based HTTP loader.

const path = "mobilenet/model.json"
const mn = new mobilenet.MobileNet(1, 1);
mn.path = `file://${path}`
await mn.load()

MobileNet Models

TensorFlow.js models are made up of two file types: a model configuration file in JSON and model weights in binary format. Model weights are frequently sharded into multiple files for better browser caching.

The automatic loading code for MobileNet models retrieves model configuration and weight shards from a public storage bucket at this address.

https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v${version}_${alpha}_${size}/

The URL template parameters refer to the model versions listed here. On that page, the classification accuracy results for each version are also displayed.

According to the source code, the tensorflow-models/mobilenet library can only load MobileNet v1 models.

The HTTP retrieval code loads the model.json file from this location and then recursively retrieves all model weights shards that are referenced. These files are in the groupX-shard1of1 format.

Make Angular 13, MongoDB example with Node.js Express

The tutorial is an Angular 13 + MongoDB example with Node.js Express: CRUD. You can follow the introduction to create CRUD apps that use Angular 13 and Node js express rest API and MongoDB.

Following  the below steps to create Angular 13 + Node JS MongoDB crud application:

  • Step 1 – Create New Angular App
  • Step 2 – Create Components in Angular
  • Step 3 – Import Modules in app.module.ts
  • Step 4 – Create CRUD Routes
  • Step 5 – Build RESTful API using Node + Express js + MongoDB
  • Step 6 – Create Angular Service for REST API Consumption
  • Step 7 – Add code In app.component.html
  • Step 8 – Create Operation

Step 1 – Create New Angular App

First of all, open your terminal and execute the following command on it to install an Angular app:

Firstly, open your terminal and execute the  command to install an Angular app:

ng new my-new-app

Next, you need to execute the following command on the terminal to install angular material:

ng add @angular/material

Step 2 – Create Components in Angular

In this step, execute the following commands on the terminal to generate components in the angular application. Then, open your terminal and execute the below command:

ng g c components/add-book
ng g c components/book-detail
ng g c components/books-list

Step 3 – Import Modules in app.module.ts

In this step, visit the directory src/app and then open the file app.module.ts. After that you need to add the below code into the file:

...
...
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
  declarations: [],
  imports: [
    FormsModule,
    HttpClientModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: []
})
export class AppModule { }

Step 4 – Create CRUD Routes

In the step, you need to create routes with the help of Angular routes. You will make a consensus with components to enable the navigation in the CRUD application, then you need to add the following code in the app-routing.module.ts file.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BooksListComponent } from './components/books-list/books-list.component';
import { AddBookComponent } from './components/add-book/add-book.component';
import { BookDetailComponent } from './components/book-detail/book-detail.component';
const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'add-book' },
  { path: 'books-list', component: BooksListComponent },
  { path: 'add-book', component: AddBookComponent },
  { path: 'edit-book/:id', component: BookDetailComponent }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Step 5 – Build RESTful API using Node + Express js + MongoDB

In the step, we will introduce how to create RESTful API with Node and Express.js. We will learn to use MongoDB and how to handle data.

You need to execute the following command to invoke the REST API development with Node and Express.js:

mkdir node-rest-api && cd node-rest-api

Then you need to execute the command:

npm init -y

Next, you execute the below commands to install imperative npm packages that will help you to create REST APIs for your Angular CRUD system:

npm install express cors body-parser mongoose
npm install nodemon --save-dev

In order to store the data flawlessly, you can use a reliable database of what else could be a better choice than MongoDB. So, execute the command to create a node-backend/database directory, and generate the db.js file where all the logic will be piled up for invoking the MongoDB connection.

mkdir database && cd database && touch db.js

Next, you need to add the following code in node-backend/database/db.js file.

module.exports = {
    db: 'mongodb://localhost:27017/db'
};

Now, you have to create the Book model or schema, and create node-backend/model folder. Also, you need to create a Book.js file and add the below code into the file:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let Book = new Schema({
  name: {
    type: String
  },
  price: {
    type: String
  },
  description: {
    type: String
  }
}, {
  collection: 'books'
})
module.exports = mongoose.model('Book', Book)

Then, you need to define the REST API routes using Express js in the node project. Then you can create node-backend/routes folder, and create book.routes.js file. You need to add the below code into it:

const express = require('express');
const app = express();
const bookRoute = express.Router();
let Book = require('../model/Book');
// Add Book
bookRoute.route('/add-book').post((req, res, next) => {
    Book.create(req.body, (error, data) => {
    if (error) {
      return next(error)
    } else {
      res.json(data)
    }
  })
});
// Get all Book
bookRoute.route('/').get((req, res) => {
    Book.find((error, data) => {
    if (error) {
      return next(error)
    } else {
      res.json(data)
    }
  })
})
// Get Book
bookRoute.route('/read-book/:id').get((req, res) => {
    Book.findById(req.params.id, (error, data) => {
    if (error) {
      return next(error)
    } else {
      res.json(data)
    }
  })
})
// Update Book
bookRoute.route('/update-book/:id').put((req, res, next) => {
    Book.findByIdAndUpdate(req.params.id, {
    $set: req.body
  }, (error, data) => {
    if (error) {
      return next(error);
      console.log(error)
    } else {
      res.json(data)
      console.log('Book updated successfully!')
    }
  })
})
// Delete Book
bookRoute.route('/delete-book/:id').delete((req, res, next) => {
    Book.findByIdAndRemove(req.params.id, (error, data) => {
    if (error) {
      return next(error);
    } else {
      res.status(200).json({
        msg: data
      })
    }
  })
})
module.exports = bookRoute;

Now, sum up all the code and conjugate it in one place so that you can run your backend and propel the CRUD app development forward.

Next, you create and add the following code in the index.js file:

let express = require('express'),
  path = require('path'),
  mongoose = require('mongoose'),
  cors = require('cors'),
  bodyParser = require('body-parser'),
  mongoDb = require('./database/db');
mongoose.Promise = global.Promise;
mongoose.connect(mongoDb.db, {
  useNewUrlParser: true,
  useFindAndModify: false,
  useUnifiedTopology: true
}).then(() => {
    console.log('Database sucessfully connected ')
  },
  error => {
    console.log('Database error: ' + error)
  }
)
const bookRoute = require('./routes/book.routes')
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));
app.use(cors());
// Static directory path
app.use(express.static(path.join(__dirname, 'dist/angular-mean-crud-tutorial')));
// API root
app.use('/api', bookRoute)
// PORT
const port = process.env.PORT || 8000;
app.listen(port, () => {
  console.log('Listening on port ' + port)
})
// 404 Handler
app.use((req, res, next) => {
  next(createError(404));
});
// Base Route
app.get('/', (req, res) => {
  res.send('invaild endpoint');
});
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'dist/angular-mean-crud-tutorial/index.html'));
});
// error handler
app.use(function (err, req, res, next) {
  console.error(err.message);
  if (!err.statusCode) err.statusCode = 500;
  res.status(err.statusCode).send(err.message);
});

Step 6 – Create Angular Service for REST API Consumption

In the step, you need to visit app/service directory in Angular project and create Book.ts class within.

Then, you add the following code in app/service/Book.ts file:

export class Book {
    _id!: String;
    name!: String;
    price!: String;
    description!: String;
}

Then, you need to execute the command to create a crud service file:

ng g s service/crud

Then, you need to add the below code in app/service/crud.service.ts file:

import { Injectable } from '@angular/core';
import { Book } from './Book';
import { catchError, map } from 'rxjs/operators';
import { Observable, throwError } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
@Injectable({
  providedIn: 'root'
})
export class CrudService {
  // Node/Express API
  REST_API: string = 'http://localhost:8000/api';
  // Http Header
  httpHeaders = new HttpHeaders().set('Content-Type', 'application/json');
  constructor(private httpClient: HttpClient) { }
  // Add
  AddBook(data: Book): Observable<any> {
    let API_URL = `${this.REST_API}/add-book`;
    return this.httpClient.post(API_URL, data)
      .pipe(
        catchError(this.handleError)
      )
  }
  // Get all objects
  GetBooks() {
    return this.httpClient.get(`${this.REST_API}`);
  }
  // Get single object
  GetBook(id:any): Observable<any> {
    let API_URL = `${this.REST_API}/read-book/${id}`;
    return this.httpClient.get(API_URL, { headers: this.httpHeaders })
      .pipe(map((res: any) => {
          return res || {}
        }),
        catchError(this.handleError)
      )
  }
  // Update
  updateBook(id:any, data:any): Observable<any> {
    let API_URL = `${this.REST_API}/update-book/${id}`;
    return this.httpClient.put(API_URL, data, { headers: this.httpHeaders })
      .pipe(
        catchError(this.handleError)
      )
  }
  // Delete
  deleteBook(id:any): Observable<any> {
    let API_URL = `${this.REST_API}/delete-book/${id}`;
    return this.httpClient.delete(API_URL, { headers: this.httpHeaders}).pipe(
        catchError(this.handleError)
      )
  }
  // Error
  handleError(error: HttpErrorResponse) {
    let errorMessage = '';
    if (error.error instanceof ErrorEvent) {
      // Handle client error
      errorMessage = error.error.message;
    } else {
      // Handle server error
      errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
    }
    console.log(errorMessage);
    return throwError(errorMessage);
  }
}

Step 7 – Add code In app.component.html

In the step, you need to create HTML and for crud app in Angular. You need to visit src/app/app.component.html and update the below code into the HTML file:

<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <a class="navbar-brand">Angular 13 CRUD Operations Demo</a>
  <div id="navbarNav" class="collapse navbar-collapse">
    <ul class="navbar-nav ml-auto ">
      <li class="nav-item">
        <a class="nav-link" routerLinkActive="active" routerLink="/books-list">Show Books</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" routerLinkActive="active" routerLink="/add-book">Add Books</a>
      </li>
    </ul>
  </div>
</nav>
<router-outlet></router-outlet>

Step 8 – Create Operation

After that, you can add the code in the add-book.component.ts file:

import { Component, OnInit, NgZone } from '@angular/core';
import { Router } from '@angular/router';
import { CrudService } from './../../service/crud.service';
import { FormGroup, FormBuilder } from "@angular/forms";
@Component({
  selector: 'app-add-book',
  templateUrl: './add-book.component.html',
  styleUrls: ['./add-book.component.scss']
})
export class AddBookComponent implements OnInit {
  bookForm: FormGroup;
  
  constructor(
    public formBuilder: FormBuilder,
    private router: Router,
    private ngZone: NgZone,
    private crudService: CrudService
  ) {
    this.bookForm = this.formBuilder.group({
      name: [''],
      price: [''],
      description: ['']
    })
  }
  ngOnInit() { }
  onSubmit(): any {
    this.crudService.AddBook(this.bookForm.value)
    .subscribe(() => {
        console.log('Data added successfully!')
        this.ngZone.run(() => this.router.navigateByUrl('/books-list'))
      }, (err) => {
        console.log(err);
    });
  }
}

Then, you need to add the following code in the add-book.component.html file:

<div class="row justify-content-center mt-5">
    <div class="col-md-4">
        <form [formGroup]="bookForm" (ngSubmit)="onSubmit()">
          <div class="form-group">
            <label>Name</label>
            <input class="form-control" type="text" formControlName="name" required>
          </div>
  
          <div class="form-group">
            <label>Price</label>
            <input class="form-control" type="text" formControlName="price" required>
          </div>
  
          <div class="form-group">
            <label>Description</label>
            <input class="form-control" type="text" formControlName="description" required>
          </div>
  
          <div class="form-group">
            <button class="btn btn-primary btn-block" type="submit">Add Book</button>
          </div>
        </form>
    </div>
  </div>

Next, you add the below code in the books-list.component.ts file:

import { Component, OnInit } from '@angular/core';
import { CrudService } from './../../service/crud.service';
@Component({
  selector: 'app-books-list',
  templateUrl: './books-list.component.html',
  styleUrls: ['./books-list.component.scss']
})
export class BooksListComponent implements OnInit {
  
  Books:any = [];
  constructor(private crudService: CrudService) { }
  ngOnInit(): void {
    this.crudService.GetBooks().subscribe(res => {
      console.log(res)
      this.Books =res;
    });   
  }
  delete(id:any, i:any) {
    console.log(id);
    if(window.confirm('Do you want to go ahead?')) {
      this.crudService.deleteBook(id).subscribe((res) => {
        this.Books.splice(i, 1);
      })
    }
  }
}

Next, you add the below code in the books-list.component.html file:

<div class="container">
  <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
    <h2 class="h2">Books List</h2>
  </div>
  <div class="table-responsive">
    <table class="table table-bordered">
      <thead>
        <tr>
          <th scope="col">Id</th>
          <th scope="col">Name</th>
          <th scope="col">Price</th>
          <th scope="col">Description</th>
          <th class="text-center" scope="col">Action</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let book of Books; let i = index">
          <th scope="row">{{book._id}}</th>
          <td>{{book.name}}</td>
          <td>{{book.price}}</td>
          <td>{{book.description}}</td>
          <td class="text-center">
            <button class="btn btn-sm btn-primary" routerLink="/edit-book/{{book._id}}">Edit</button>
            <button class="btn btn-sm btn-danger" (click)="delete(book._id, i)">Delete</button>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Next, add the below code in the book-detail.component.ts file:

import { Component, OnInit, NgZone } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { CrudService } from './../../service/crud.service';
import { FormGroup, FormBuilder } from "@angular/forms";
@Component({
  selector: 'app-book-detail',
  templateUrl: './book-detail.component.html',
  styleUrls: ['./book-detail.component.scss']
})
export class BookDetailComponent implements OnInit {
  getId: any;
  updateForm: FormGroup;
  
  constructor(
    public formBuilder: FormBuilder,
    private router: Router,
    private ngZone: NgZone,
    private activatedRoute: ActivatedRoute,
    private crudService: CrudService
  ) {
    this.getId = this.activatedRoute.snapshot.paramMap.get('id');
    this.crudService.GetBook(this.getId).subscribe(res => {
      this.updateForm.setValue({
        name: res['name'],
        price: res['price'],
        description: res['description']
      });
    });
    this.updateForm = this.formBuilder.group({
      name: [''],
      price: [''],
      description: ['']
    })
  }
  ngOnInit() { }
  onUpdate(): any {
    this.crudService.updateBook(this.getId, this.updateForm.value)
    .subscribe(() => {
        console.log('Data updated successfully!')
        this.ngZone.run(() => this.router.navigateByUrl('/books-list'))
      }, (err) => {
        console.log(err);
    });
  }
}

Next, add the below code in the book-detail.component.html file:

<div class="row justify-content-center mt-5">
  <div class="col-md-4">
    <form [formGroup]="updateForm" (ngSubmit)="onUpdate()">
      <div class="form-group">
        <label>Name</label>
        <input class="form-control" type="text" formControlName="name" required>
      </div>
      <div class="form-group">
        <label>Price</label>
        <input class="form-control" type="text" formControlName="price" required>
      </div>
      <div class="form-group">
        <label>Description</label>
        <input class="form-control" type="text" formControlName="description" required>
      </div>
      <div class="form-group">
        <button class="btn btn-primary btn-block" type="submit">Update</button>
      </div>
    </form>
  </div>
</div>

For the topic of Angular 13 CRUD application using Node.js, Express.js, and MongoDB. In the tutorial, we introduce how to create a crud app in Angular 13 using Node js and Mongo DB with built REST API.

Add social based login (Google, Facebook, and Github) to your Spring Boot application using OAuth2 functionalities

In the tutorial, we will explain how to add social-based login to a Spring boot application using a Spring Security function called OAuth2. We use MySQL database and React for the frontend.

App Screenshot

Creating the Project

You can create a project using the Spring Initializr web tool http://start.spring.io, follows the introduction to fill in the information:

  • Artifact: ozenero-spring-social
  • Dependencies: Spring Web, Spring Security, OAuth2 Client, Spring Data JPA, MySQL Driver, Validation

After that, you can click Generate and download your project

Creating OAuth2 apps for social login

You need to create an app in OAuth2 provider console to enable social login with an OAuth2 provider. After that, you can get AppID and AppSecret (also called ClientId and Client Secret). OAuth2 providers use the AppID and AppSecret to identify your app. Some other settings include:

  • Authorized redirect URIs: A valid list of redirect URIs to redirect users after granting or rejecting permission to the app.
  • Scope: used to ask users for permission to access data.

Creating Facebook, Github, and Google Apps

Configuring the Spring Boot application

By default, Spring boot reads configurations from the file src/main/resource/application.properties. It also supports .yaml format. Rename application.properties file to application.yaml and add the following configurations.

spring:
    datasource:
        url: jdbc:mysql://localhost:3306/spring_social?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
        username: root
        password: callicoder

    jpa:
        show-sql: true
        hibernate:
            ddl-auto: update
            naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
        properties:
            hibernate:
                dialect: org.hibernate.dialect.MySQL5InnoDBDialect
    security:
      oauth2:
        client:
          registration:
            google:
              clientId: your_id.apps.googleusercontent.com
              clientSecret: your_secret
              redirectUri: "{baseUrl}/oauth2/callback/{registrationId}"
              scope:
                - email
                - profile
            facebook:
              clientId: your_id
              clientSecret: your_secret
              redirectUri: "{baseUrl}/oauth2/callback/{registrationId}" # Note that facebook now mandates the use of https redirect URIs, so make sure your app supports https in production
              scope:
                - email
                - public_profile
            github:
              clientId: your_id
              clientSecret: your_secret
              redirectUri: "{baseUrl}/oauth2/callback/{registrationId}"
              scope:
                - user:email
                - read:user
          provider:
            facebook:
              authorizationUri: https://www.facebook.com/v3.0/dialog/oauth
              tokenUri: https://graph.facebook.com/v3.0/oauth/access_token
              userInfoUri: https://graph.facebook.com/v3.0/me?fields=id,first_name,middle_name,last_name,name,email,verified,is_verified,picture.width(250).height(250)
app:
  auth:
    tokenSecret: your_token_secret
    tokenExpirationMsec: 864000000
  cors:
    allowedOrigins: http://localhost:3000 # Comma separated list of allowed origins
  oauth2:
    # After successfully authenticating with the OAuth2 Provider,
    # we'll be generating an auth token for the user and sending the token to the
    # redirectUri mentioned by the client in the /oauth2/authorize request.
    # We're not using cookies because they won't work well in mobile clients.
    authorizedRedirectUris:
      - http://localhost:3000/oauth2/redirect
      - myandroidapp://oauth2/redirect
      - myiosapp://oauth2/redirect

The data source includes MySQL database configurations. Database name is spring_social, please modify spring.datasource.username and spring.datasource.password.

In security:oauth2:client:registration:, the configurations include all details of oauth2 providers. Inapp.auth, its config the JWT authentication token when the user logged in successfully.

In redirectUriTemplate property in all the registered oauth2 providers, when you create an app in the OAuth2 providers, you have to add an authorized redirect URI which matches the template. In this case, for a google app, we config the authorizedRedirectURI http://localhost:8080/oauth2/callback/google.

Binding AppProperties

In the next step, we bind all configurations prefixed with app to a POJO class using Spring Boot’s @ConfigurationProperties feature

package com.example.springsocial.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.ArrayList;
import java.util.List;

@ConfigurationProperties(prefix = "app")
public class AppProperties {
    private final Auth auth = new Auth();
    private final OAuth2 oauth2 = new OAuth2();

    public static class Auth {
        private String tokenSecret;
        private long tokenExpirationMsec;

        public String getTokenSecret() {
            return tokenSecret;
        }

        public void setTokenSecret(String tokenSecret) {
            this.tokenSecret = tokenSecret;
        }

        public long getTokenExpirationMsec() {
            return tokenExpirationMsec;
        }

        public void setTokenExpirationMsec(long tokenExpirationMsec) {
            this.tokenExpirationMsec = tokenExpirationMsec;
        }
    }

    public static final class OAuth2 {
        private List<String> authorizedRedirectUris = new ArrayList<>();

        public List<String> getAuthorizedRedirectUris() {
            return authorizedRedirectUris;
        }

        public OAuth2 authorizedRedirectUris(List<String> authorizedRedirectUris) {
            this.authorizedRedirectUris = authorizedRedirectUris;
            return this;
        }
    }

    public Auth getAuth() {
        return auth;
    }

    public OAuth2 getOauth2() {
        return oauth2;
    }
}

Enabling AppProperties

In order to enable the properties, we add the@EnableConfigurationProperties annotation. Let’s open the main application class SpringSocialApplication.java and add the annotation like below.

package com.example.springsocial;

import com.example.springsocial.config.AppProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class SpringSocialApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringSocialApplication.class, args);
	}
}

Enabling CORS

In order to enable CORS to allow our frontend client can access the APIs from a different origin. We need to enable the origin http://localhost:3000 where the frontend application will be running.

package com.example.springsocial.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    private final long MAX_AGE_SECS = 3600;

    @Value("${app.cors.allowedOrigins}")
    private String[] allowedOrigins;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
        .allowedOrigins(allowedOrigins)
        .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
        .allowedHeaders("*")
        .allowCredentials(true)
        .maxAge(MAX_AGE_SECS);
    }
}

Creating the database entities

Let’s now create the Entity classes of our application. Following is the definition of the User class –

In the next step, we create Entity classes for the application. You can define the User class like below.

package com.example.springsocial.model;
import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;

@Entity
@Table(name = "users", uniqueConstraints = {
        @UniqueConstraint(columnNames = "email")
})
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Email
    @Column(nullable = false)
    private String email;

    private String imageUrl;

    @Column(nullable = false)
    private Boolean emailVerified = false;

    @JsonIgnore
    private String password;

    @NotNull
    @Enumerated(EnumType.STRING)
    private AuthProvider provider;

    private String providerId;

    // Getters and Setters (Omitted for brevity)
}

The User class contains information about the authentication provider. Following is the definition of the AuthProvider enum –

In the User class, it provides information related to the authentication provider. Here is the definition of the AuthProvider enum.

package com.example.springsocial.model;

public enum  AuthProvider {
    local,
    facebook,
    google,
    github
}

Creating the repositories for accessing data from the database

Let’s create the repository layer for accessing data from the database. The following UserRepository interface provides database functionalities for the User entity. Thanks to Spring-Data-JPA, we don’t need to write much code here

In order to create the repository layer for accessing data from the database, we need to config theUserRepository interface to provide database functionalities for the User entity. Refer to Spring-Data-JPA  for the code

package com.example.springsocial.repository;

import com.example.springsocial.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findByEmail(String email);

    Boolean existsByEmail(String email);

}

In this article, we introduce how to configure an application and defined the entity classes and repositories.

Tutorial to use Tuya Cloud Development Platform for your first IoT Development Practice

In the tutorial, we will introduce how to configure a Tuya Cloud Project, add devices, create a simulation with virtual devices, debug devices, and use Tuya-connector-Python SDK to develop a program to control ‘Powered by Tuya’ devices.

Configure Cloud Project

After registering an account at https://iot.tuya.com/, log in and go to Cloud>Development, choose to Create Cloud Project.

Fill the information in Project Name, Description, Development Method, Industry, Availability Zone.

To understand about development method, see Development Method.

Click Create and subscribe to the API products On the Authorize API Products page -> Click Authorize

Next, create the original asset and original account

Add Virtual device

Add devices with IoT Device Management app

In order to add your physical devices, you need to install the IoT Device Management app. After installing the IoT Device Management app, click Add Device -> Add Devices with IoT Device Management App, and the Scan the QR code with the IoT Device Management app to authorize the project.

You can log in to the IoT Device Management app using the initial account configured when the project was created. You also manage your account and the Tab Users.

After login, you can create your Assets and Pair your Smart Device.

A device can be added in two methods, Add Manually and Auto Scan. They are similar to those used to add devices on all-in-one apps. For more information, see Add a device.

Make a simulation with Virtual devices

You can create a simulation by adding Virtual devices to your cloud project. Choose Cloud->Development->Devices->All Devices->Add Device->Add Virtual Device

You can choose different types of devices for your application such and home security, household appliance, Health & Personal Care.

You can click “Buy” the physical devices at a good price. Tuya also provides OEM solutions for your product, when you can customize Logo, packing, User material, and user apps.

 

Debug Devices

You can make your simulation and use debugging feature to send instructions to control your devices.

Choose Cloud -> Development -> select your project -> Devices -> Debug Devices

Modify your  Value in Instruction Set -> Click Send Instruction

You also can modify the Instruction Set by choosing JSON Editing

You can click Update Device Status to get the Lastest status of the device.

Click Device Logs, you can query logs by DP ID, device event, and time.

Develop a program to control ‘Powered by Tuya’ devices

Next step, we can use Tuya-connector-Python SDK to control your devices through your Cloud development projects.

Firstly, download the SDK at Tuya-connector-Python.

You need to install a tuya-connector-python on your computer for requirements.

pip3 install tuya-connector-python

You need to modify ACCESS_ID, ACCESS_KEY, API_ENDPOINT, MQ_ENDPOINT to connect to your Cloud Project.

Go to Overview Tab to get that parameters

ACCESS_ID and ACCESS_KEY: Enter the values of Access ID and Access Key in the Authorization Key section on the Cloud Development Platform. For more information, see Query project information.

API_ENDPOINT: the data center address of API requests. See the tuya-connector-python/tuya_connector/tuya_enums.py file.

MQ_ENDPOINT: the request address of message subscription.

Location API_ENDPOINT MQ_ENDPOINT
China https://openapi.tuyacn.com wss://mqe.tuyacn.com:8285/
America https://openapi.tuyaus.com wss://mqe.tuyaus.com:8285/
Europe https://openapi.tuyaeu.com wss://mqe.tuyaeu.com:8285/
India https://openapi.tuyain.com wss://mqe.tuyain.com:8285/

How to Control Devices with Python

Use the sample code of querying devices and issuing instructions: device_control.py.

import logging
from tuya_connector import TuyaOpenAPI, TUYA_LOGGER

ACCESS_ID = "yhyrrpky*****gc0xmj9"
ACCESS_KEY = "ad976bf8*****da383ec37311e65f554"
API_ENDPOINT = "https://openapi.tuyacn.com"

# Enable debug log
TUYA_LOGGER.setLevel(logging.DEBUG)

# Init OpenAPI and connect
openapi = TuyaOpenAPI(API_ENDPOINT, ACCESS_ID, ACCESS_KEY)
openapi.connect()

# Set up device_id
DEVICE_ID ="vdevo164024617876854"

# Call APIs from Tuya
# Get the device information
response = openapi.get("/v1.0/iot-03/devices/{}".format(DEVICE_ID))

# Get the instruction set of the device
response = openapi.get("/v1.0/iot-03/devices/{}/functions".format(DEVICE_ID))


# Send commands
commands = {'commands': [{'code': 'countdown_1', 'value': 200}]}
openapi.post('/v1.0/iot-03/devices/{}/commands'.format(DEVICE_ID), commands)

# Get the status of a single device
response = openapi.get("/v1.0/iot-03/devices/{}/status".format(DEVICE_ID))

Modify DEVICE_ID with the ID of the device you want to control. You can get the Device ID at Development->Devices->All Devices

In the above code, sent commands are modified by, you should modify the code and the value depend on your devices.

commands = {'commands': [{'code': 'switch_led', 'value': true}]} openapi.post('/v1.0/iot-03/devices/{}/commands'.format(DEVICE_ID), commands)

The return should like below


[tuya-openapi] Request: method = GET, url = https://openapi.tuyacn.com/v1.0/token, params = {'grant_type': 1}, body = None, t = 1640339456098
[2021-12-24 17:50:57,518] [tuya-openapi] Response: {
"result": {
"access_token": "***",
"expire_time": 7200,
"refresh_token": "***",
"uid": "***"
},
"success": true,
"t": 1640339457334
}
[2021-12-24 17:50:57,519] [tuya-openapi] Request: method = GET, url = https://openapi.tuyacn.com/v1.0/iot-03/devices/vdevo164024617876854, params = None, body = None, t = 1640339457519
[2021-12-24 17:50:57,964] [tuya-openapi] Response: {
"result": {
"active_time": 1640246178,
"asset_id": "1473886148148346880",
"category": "cz",
"category_name": "Socket",
"create_time": 1640246178,
"gateway_id": "",
"icon": "smart/icon/1498529014_0.png",
"id": "vdevo164024617876854",
"ip": "***",
"lat": "***",
"local_key": "***",
"lon": "***",
"model": "HYS-X5-NEW",
"name": "Mini Smart Socket-vdevo",
"online": true,
"product_id": "qoacsyyfbrbe3lm8",
"product_name": "Mini Smart Socket",
"sub": false,
"time_zone": "+08:00",
"update_time": 1640246178,
"uuid": "vdevo164024617876854"
},
"success": true,
"t": 1640339457780
}
[2021-12-24 17:50:57,965] [tuya-openapi] Request: method = GET, url = https://openapi.tuyacn.com/v1.0/iot-03/devices/vdevo164024617876854/functions, params = None, body = None, t = 1640339457965
[2021-12-24 17:50:58,382] [tuya-openapi] Response: {
"result": {
"category": "cz",
"functions": [
{
"code": "switch_1",
"desc": "switch 1",
"name": "switch 1",
"type": "Boolean",
"values": "{}"
},
{
"code": "countdown_1",
"desc": "countdown 1",
"name": "countdown 1",
"type": "Integer",
"values": "{\"unit\":\"s\",\"min\":0,\"max\":86400,\"scale\":0,\"step\":1}"
}
]
},
"success": true,
"t": 1640339458198
}
[2021-12-24 17:50:58,382] [tuya-openapi] Request: method = POST, url = https://openapi.tuyacn.com/v1.0/iot-03/devices/vdevo164024617876854/commands, params = None, body = {'commands': [{'code': 'countdown_1', 'value': 200}]}, t = 1640339458382
[2021-12-24 17:50:58,853] [tuya-openapi] Response: {
"result": true,
"success": true,
"t": 1640339458669
}
[2021-12-24 17:50:58,853] [tuya-openapi] Request: method = GET, url = https://openapi.tuyacn.com/v1.0/iot-03/devices/vdevo164024617876854/status, params = None, body = None, t = 1640339458853
[2021-12-24 17:50:59,284] [tuya-openapi] Response: {
"result": [
{
"code": "switch_1",
"value": false
},
{
"code": "countdown_1",
"value": 200
}
],
"success": true,
"t": 1640339459099
}

In the tutorial, we introduce how to create a Tuya Cloud Project, add devices and create Virtual devices to make a simulation for your different scenarios. Tuya provides APIs and SDK in Python to help you control your smart devices through Tuya Cloud Development Platform. You can apply and extend this tutorial for your experiment with Tuya Platform to build your IoT applications and services fastly.

In the next tutorial, we want to show you “how to design a Smart Security app for home and small business users”. In order to implement DIY home security, the Tuya platform can support Video surveillance, voice chat, and smart control. You can add smart devices and set up a smart home scenario. You can also view live video and cloud-stored video, and communicate timely by voice.