How to Create REST with Spring-rest-2-ts and TypeScript

How to Create REST with Spring-rest-2-ts and TypeScript

In this post, we discuss a Spring REST TypeScript generator that creates models and services that reflect backend models and REST services. I would like to introduce how you can REST with Spring-rest-2-ts TypeScript generator.

If your development setup uses the Spring framework on the backend and Angular or React on the frontend side, you can experience the same benefits that we did. In this short article, I would like to introduce how you can REST with our spring-rest-2-ts TypeScript generator.

Examples

To get an idea of what spring-rest2ts generator can do, let's create a simple model and REST controller in Java, and we will show what will be generated in TypeScript

public class BaseDTO {
    private int id;
    @JsonFormat(shape = JsonFormat.Shape.NUMBER)
    private Date updateTimeStamp;
 }

public class OrderDTO extends BaseDTO {
    private double price;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
    private LocalDateTime orderTimestamp;
}

Spring REST controller:

@Controller
@RequestMapping("api/order")
public class OrderCtrl {

    @PostMapping(consumes = {"application/json"}, produces = {"application/json"})
    @ResponseBody
    @ResponseStatus(HttpStatus.CREATED)
    public OrderDTO createOrder(@RequestBody OrderDTO entity) {
        return entity;
    }

    @RequestMapping(path = "/{id}", method = RequestMethod.GET, produces = {"application/json"})
    @ResponseBody
    public OrderDTO getOrder(@PathVariable int id) {
        return new OrderDTO();
    }

}

In TypeScript, for DTO classes, we get two interfaces with mapped inheritance, where each field is mapped to their respective TypeScript types:

export interface Base {
    id: number;
    updateTimeStamp: number;
}

export interface Order extends Base {
    price: number;
    /**
     *    pattern : dd-MM-yyyy hh:mm:ss
     */
    orderTimestamp: string;
}

As we see, if a field has Jackson annotation, it is taken into account. If not, its transformation is based on Java types to TypeScript mapping. There is support for Type names mapping. In Java, we see that there is OrderDTO by providing proper name mapper which cuts off postfix DTO and we get type Order

Observable Based Service

The mapping of model classes is quite easy to understand. What's more interesting is the mapping of Spring REST controllers for which, in TypeScript, there is a generated implementation to call endpoints. Such an approach hides under method names, paths, and parameters, so the code will be resistant to changes on the backend. What is more important is that we transform return types to selected web frameworks; for Angular 2+ there is a generated valid Angular service ready to use for injection:

@Injectable()
export class OrderService {
    httpService: HttpClient;

    public constructor(httpService: HttpClient) {
        this.httpService = httpService;
    }

    public createOrder(entity: Order): Observable<Order> {
        let headers = new HttpHeaders().set('Content-type', 'application/json');
        return this.httpService.post<Order>('api/order', entity, {headers});
    }

    public getOrder(id: number): Observable<Order> {
        return this.httpService.get<Order>('api/order/' + id + '');
    }

}

OrderService is generated for OrderCtrl. Here, the type name was also transformed by the type name mapper. If the REST API is not available on the same host as the web application, there is a possibility to configure baseURL, which could be a path prefix o entire host reference

Promise Based Service

For web frameworks that are using the Promise API generator, proper configuration is also able to generate a service class:

export class OrderService {
    baseURL: URL;

    public constructor(baseURL: URL = new URL(window.document.URL)) {
        this.baseURL = baseURL;
    }

    public createOrder(entity: Order): Promise<Order> {
        const url = new URL('/api/order', this.baseURL);

        return fetch(url.toString(), {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(entity)
        }).then(res => res.json());
    }

    public getOrder(id: number): Promise<Order> {
        const url = new URL('/api/order/' + id + '', this.baseURL);

        return fetch(url.toString(), {method: 'GET'}).then(res => res.json());
    }

}
Configuration

Due to the greater flexibility, TypeScript generator is configured by code; no configuration files are needed. This gives a possibility to easily extend generator in places where it is needed. Here is the simplest generator configurator:

    Rest2tsGenerator tsGenerator = new Rest2tsGenerator();

    // Java Classes filtering
    tsGenerator.setModelClassesCondition(new ExtendsJavaTypeFilter(BaseDTO.class));
    tsGenerator.setRestClassesCondition(new ExtendsJavaTypeFilter(BaseCtrl.class));

    // Java model classes converter setup
    JacksonObjectMapper jacksonObjectMapper = new JacksonObjectMapper();
    jacksonObjectMapper.setFieldsVisibility(JsonAutoDetect.Visibility.ANY);
    modelClassesConverter = new ModelClassesToTsInterfacesConverter(jacksonObjectMapper);
    modelClassesConverter.setClassNameMapper(new SubstringClassNameMapper("DTO", ""));
    tsGenerator.setModelClassesConverter(modelClassesConverter);

    // Spring REST controllers converter
    restClassesConverter = new SpringRestToTsConverter(new Angular4ImplementationGenerator());
    restClassesConverter.setClassNameMapper(new SubstringClassNameMapper("Ctrl", "Service"));
    tsGenerator.setRestClassesConverter(restClassesConverter);

    // set of java root packages for class scanning
    javaPackageSet = Collections.singleton("com.blueveery.springrest2ts.examples");
    tsGenerator.generate(javaPackageSet, Paths.get("../target/ts-code"));

Thank you for reading !

How to create a simple web application with Java 8, Spring Boot and Angular

How to create a simple web application with Java 8, Spring Boot and Angular

In this tutorial, we'll look at how developers can combine multiple technologies to make a web application. Read on to get started!

Pre-Requisites for Getting Started
  • Java 8 is installed.
  • Any Java IDE (preferably STS or IntelliJ IDEA).
  • Basic understanding of Java and Spring-based web development and UI development using HTML, CSS, and JavaScript.
Backdrop

In this article, I will try to create a small end-to-end web application using Java 8 and Spring Boot.

I have chosen SpringBoot because it is much easier to configure and plays well with other tech stacks. I have also used a REST API and SpringData JPA with an H2 database.

I used Spring Initializer to add all the dependencies and create a blank working project with all my configurations.

I have used Maven as the build tool, though Gradle can also be used.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>bootdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>bootDemo</name>
    <description>Demo project for Spring Boot</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath />
        <!-- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.restdocs</groupId>
            <artifactId>spring-restdocs-mockmvc</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

In the UI part, I have used AngularJS and BootStrap CSS along with basic JS, CSS, and HTML.

I tried to follow the coding guidelines as much as I could, but all suggestions are welcome.

This is a very simple project which can be useful for creating an end-to-end web application.

Package Structure

Implementation

Let's start with the SpringBootApplication class.

@SpringBootApplication
public class BootDemoApplication {
 @Autowired
 UserRepository userRepository;

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

Let's create Controllers now. 

@Controller
public class HomeController {
 @RequestMapping("/home")
 public String home() {
  return "index";
 }
}

This will act as the homepage for our SPA. Now we create a Controller to handle some REST calls.

@RequestMapping("/user")
@RestController
public class UserController {
 @Autowired
 UserService userService;

 @RequestMapping(Constants.GET_USER_BY_ID)
 public UserDto getUserById(@PathVariable Integer userId) {
  return userService.getUserById(userId);
 }

 @RequestMapping(Constants.GET_ALL_USERS)
 public List < UserDto > getAllUsers() {
  return userService.getAllUsers();
 }

 @RequestMapping(value = Constants.SAVE_USER, method = RequestMethod.POST)
 public void saveUser(@RequestBody UserDto userDto) {
  userService.saveUser(userDto);
 }
}

Here we have different methods to handle different test calls from the client side.

I have Autowired a Service class, UserService, in the Controller.

public interface UserService {
 UserDto getUserById(Integer userId);
 void saveUser(UserDto userDto);
 List < UserDto > getAllUsers();
}

@Service
public class UserServiceimpl implements UserService {
 @Autowired
 UserRepository userRepository;

 @Override
 public UserDto getUserById(Integer userId) {
  return UserConverter.entityToDto(userRepository.getOne(userId));
 }

 @Override
 public void saveUser(UserDto userDto) {
  userRepository.save(UserConverter.dtoToEntity(userDto));
 }

 @Override
 public List < UserDto > getAllUsers() {
  return userRepository.findAll().stream().map(UserConverter::entityToDto).collect(Collectors.toList());
 }
}

In a typical web application, there are generally two types of data objects: DTO (to communicate through the client) and Entity (to communicate through the DB).

DTO

public class UserDto {
    Integer userId;
    String userName;
    List<SkillDto> skillDtos= new ArrayList<>();
    public UserDto(Integer userId, String userName, List<SkillDto> skillDtos) {
        this.userId = userId;
        this.userName = userName;
        this.skillDtos = skillDtos;
    }
    public UserDto() {
    }
    public Integer getUserId() {
        return userId;
    }
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public List<SkillDto> getSkillDtos() {
        return skillDtos;
    }
    public void setSkillDtos(List<SkillDto> skillDtos) {
        this.skillDtos = skillDtos;
    }
}
public class SkillDto {
    Integer skillId;
    String SkillName;
    public SkillDto(Integer skillId, String skillName) {
        this.skillId = skillId;
        SkillName = skillName;
    }
    public SkillDto() {
    }
    public Integer getSkillId() {
        return skillId;
    }
    public void setSkillId(Integer skillId) {
        this.skillId = skillId;
    }
    public String getSkillName() {
        return SkillName;
    }
    public void setSkillName(String skillName) {
        SkillName = skillName;
    }
}

Entity

@Entity
public class User implements Serializable{
    private static final long serialVersionUID = 0x62A6DA99AABDA8A8L;
@Column
@GeneratedValue(strategy = GenerationType.AUTO)
@Id
private Integer userId;
    @Column
    private String userName;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<Skill> skills= new LinkedList<>();
    public Integer getUserId() {
        return userId;
    }
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public List<Skill> getSkills() {
        return skills;
    }
    public void setSkills(List<Skill> skills) {
        this.skills = skills;
    }
    public User() {
    }
    public User(String userName, List<Skill> skills) {
        this.userName = userName;
        this.skills = skills;
    }
}
@Entity
public class Skill {
    @Column
@GeneratedValue(strategy = GenerationType.AUTO)
@Id
private Integer skillId;
    @Column
    private String skillName;
    @ManyToOne
    private User user;
    public Skill(String skillName) {
this.skillName = skillName;
}
public Integer getSkillId() {
        return skillId;
    }
    public void setSkillId(Integer skillId) {
        this.skillId = skillId;
    }
    public String getSkillName() {
        return skillName;
    }
    public void setSkillName(String skillName) {
        this.skillName = skillName;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public Skill() {
    }
    public Skill(String skillName, User user) {
        this.skillName = skillName;
        this.user = user;
    }
}

For DB operations, we use SpringData JPA:

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

@Repository
public interface SkillRepository extends JpaRepository<Skill, Integer>{
}

Extending JpaRepository provides a lot of CRUD operations by default, and one can use it to create their own query methods as well. Please read more about this here.

To convert DTO -> Entity and Entity -> DTO, I created some basic converter classes.

public class UserConverter {
 public static User dtoToEntity(UserDto userDto) {
  User user = new User(userDto.getUserName(), null);
  user.setUserId(userDto.getUserId());
  user.setSkills(userDto.getSkillDtos().stream().map(SkillConverter::dtoToEntity).collect(Collectors.toList()));
  return user;
 }

 public static UserDto entityToDto(User user) {
  UserDto userDto = new UserDto(user.getUserId(), user.getUserName(), null);
  userDto.setSkillDtos(user.getSkills().stream().map(SkillConverter::entityToDto).collect(Collectors.toList()));
  return userDto;
 }
}

public class SkillConverter {
 public static Skill dtoToEntity(SkillDto SkillDto) {
  Skill Skill = new Skill(SkillDto.getSkillName(), null);
  Skill.setSkillId(SkillDto.getSkillId());
  return Skill;
 }

 public static SkillDto entityToDto(Skill skill) {
  return new SkillDto(skill.getSkillId(), skill.getSkillName());
 }
}

Let's focus on the UI part now.

While using Angular, there are certain guidelines we need to follow.

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="ISO-8859-1">
    <title>Main Page</title>
</head>
<body ng-app="demo">
<hr/>
<div class="container" ng-controller="UserController">
    <div class="row">
        <label>User</label> <input type="text" ng-model="userDto.userName" class="input-sm spacing"/>
        <label>Skills</label> <input type="text" ng-model="skills" ng-list class="input-sm custom-width spacing"
                                     placeholder="use comma to separate skills"/>
        <button ng-click="saveUser()" class="btn btn-sm btn-info">Save User</button>
    </div>
    <hr/>
    <div class="row">
        <p>{{allUsers | json}}</p>
    </div>
    <hr/>
    <div class="row" ng-repeat="user in allUsers">
        <div class="">
            <h3>{{user.userName}}</h3>
            <span ng-repeat="skill in user.skillDtos" class="spacing">{{skill.skillName}}</span>
        </div>
    </div>
</div>
</body>
<script src="js/lib/angular.min.js"></script>
<script src="js/lib/ui-bootstrap-tpls-2.5.0.min.js"></script>
<script src="js/app/app.js"></script>
<script src="js/app/UserController.js"></script>
<script src="js/app/UserService.js"></script>
<link rel="stylesheet" href="css/lib/bootstrap.min.css"/>
<link rel="stylesheet" href="css/app/app.css"/>
</html>

While creating the HTML, don't forget to import the required JS and CSS files.

app.js

'use strict'

var demoApp = angular.module('demo', ['ui.bootstrap', 'demo.controllers',
    'demo.services'
]);
demoApp.constant("CONSTANTS", {
    getUserByIdUrl: "/user/getUser/",
    getAllUsers: "/user/getAllUsers",
    saveUser: "/user/saveUser"
});

UserController.js

'use strict'

var module = angular.module('demo.controllers', []);
module.controller("UserController", ["$scope", "UserService",
    function($scope, UserService) {

        $scope.userDto = {
            userId: null,
            userName: null,
            skillDtos: []
        };
        $scope.skills = [];

        UserService.getUserById(1).then(function(value) {
            console.log(value.data);
        }, function(reason) {
            console.log("error occured");
        }, function(value) {
            console.log("no callback");
        });

        $scope.saveUser = function() {
            $scope.userDto.skillDtos = $scope.skills.map(skill => {
                return {
                    skillId: null,
                    skillName: skill
                };
            });
            UserService.saveUser($scope.userDto).then(function() {
                console.log("works");
                UserService.getAllUsers().then(function(value) {
                    $scope.allUsers = value.data;
                }, function(reason) {
                    console.log("error occured");
                }, function(value) {
                    console.log("no callback");
                });
 
               $scope.skills = [];
                $scope.userDto = {
                    userId: null,
                    userName: null,
                    skillDtos: []
                };
            }, function(reason) {
                console.log("error occured");
            }, function(value) {
                console.log("no callback");
            });
        }
    }
]);


UserService.js

use strict'

angular.module('demo.services', []).factory('UserService', ["$http", "CONSTANTS", function($http, CONSTANTS) {
    var service = {};
    service.getUserById = function(userId) {
        var url = CONSTANTS.getUserByIdUrl + userId;
        return $http.get(url);
    }
    service.getAllUsers = function() {
        return $http.get(CONSTANTS.getAllUsers);
    }
    service.saveUser = function(userDto) {
        return $http.post(CONSTANTS.saveUser, userDto);
    }
    return service;
}]);

app.css

body{
    background-color: #efefef;
}
span.spacing{
    margin-right: 10px;
}
input.custom-width{
    width: 200px;
}
input.spacing{
    margin-right: 5px;
}

The application can be built using:

mvn&nbsp;clean install then run as a runnable jar file by using

java -jar bootdemo-0.0.1-SNAPSHOT.jar or running the main file directly.

Open the browser and hit http://localhost:8080/home

One simple page will open. Enter the name and skills and the entered data will be persisted in the DB.

In the application.properties files, two configurations are to be added.

spring.mvc.view.prefix = /views/
spring.mvc.view.suffix = .html

The source code can be cloned or downloaded from here.

Originally published by Ashish Lohia  at dzone.com

================================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Go Full Stack with Spring Boot and Angular

Angular7 CRUD with nodejs and mysql example

Full Stack Web Development with Angular and Spring MVC

Building a Web Application Using Spring Boot, Angular, and Maven

Angular 7 + Spring Boot CRUD Example

Learning TypeScript with React

Learning TypeScript with React

Learning TypeScript with React

⚡️TL;DR: Understanding what are types, type annotations, why use them and how to use them can help you catch errors during development while also enhancing code quality and readability.

Before diving into it, let me give you a short definition of what is TypeScript (TS) taken from the official TS website.

“TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.”

Now, let me also give you a high level overview of the app’s features and how the React side is structured.

It is a one page app where it has a video player, a list of related talks and a search bar which are all built as separate React components. On page load, it fetches a default list of talks from a JSON file instead of fetching from Youtube to avoid exceeding the daily usage quota of the Youtube API when the page is loaded by many users. The app only communicates with Youtube when searching for talks.

Okay, let's start.

First, the project setup

I generated the project using Create React App with TypeScript

create-react-app my-app-name --typescript

How did I use TypeScript in this app?

Since I’m a TS beginner, I get started by learning the syntax and features of the language first specifically the types, type annotations and interfaces. In this first post, I'm gonna be talking about types and annotations only.

What is a type?

Since TypeScript is a typed language, it means we can specify/annotate the type of the variables, function parameters and object properties.

From my own understanding, a type is a symbol/representation of all the properties and methods that a variable has access to. It means if we added a type of number to a variable, the TS compiler knows that the variable has and can only access all the properties and methods that a number has.

A type is annotated to the variable, parameter or property using this format :type.
For example, let name: string.

There are a lot of available types in TS however, in this app, these are the ones that I've used:

  1. Primitive Types - number, string, boolean
  2. Arrays - by adding [] after the type (e.g. let names: string[])
  3. Video - an interface or a custom type I created to represent a video object's properties

See here for a more comprehensive list of types.

Why do we care about annotating types?

  1. Types are one of the best forms of documentation you can have. This is a very helpful to the next developer who has to read your code which could also be the future you.
  2. It helps the TypeScript compiler help you. If you annotate a variable as a number and a few lines later assigned it a value which is a string, the compiler will give you the following error.
let x: number = 123;
x = '123'; // Error: cannot assign a `string` to a `number`

Even if we remove the :number annotation from variable x, the same error will be given by the compiler because TypeScript is smart*.

*If you declare a variable with a value without specifying the type, TypeScript will figure out the type of the variable based on the initial value assigned to it.

This is called type inference.

"So if type inference is present then why do I have to bother annotating the variables?"

Type inference does not work all the time.

An example scenario is when you delay the initialization of a variable as shown below.

let age;

// a few lines later
age = 12; 
// The compiler will NOT give an error even 
// if we assign a non-numeric value to `age`
age = 'Ana'; 

If you delay the variable initialization without annotating a type to it, the variable will automatically have a type of any associated with it since the TS compiler does not have any idea what kind of properties age will have later on.

Variables with a type of any should be avoided as much as possible. It defeats the purpose of the idea behind the TS type system where we want to catch as many errors as possible during development. Why? Because, the compiler cannot do error checking on types of any.

Remember, use types to help the TS compiler help you.

How is type annotation used in the app?

Aside from using types on the variable declarations, I also used annotations in the function declarations.

A function can be annotated in the following ways:

1. Arrow function

const add = (a:number, b:number):number => {
 return a + b;
}

// or
const add: (a: number, b: number) => number = (a, b) => {
  return a + b;
};

2. Non-arrow function

function add(a:number, b:number):number {
 return a + b;
}

In both of these examples, the compiler will give an error if the function returns a string or any type that is not a number since it is expecting a number.

For fetching the channel's videos, I created a function called fetchChannelVideos which accepts a boolean flag indicating whether to fetch the default videos from the JSON file and a search query string. Both of these are optional parameters (by adding ? after the property name) which are represented as an interface. I will explain later what an interface is but for now let’s take a closer look on how the function is annotated.

interface FetchVideosArguments {
 shouldUseDefaultVideos?: boolean;
 searchQuery?: string;
}

export const fetchChannelVideos: (
 args: FetchVideosArguments
) => Promise < Video[] > = async ({
 shouldUseDefaultVideos = false,
 searchQuery
}) => {};

On the left side of the assignment operator (=),

const fetchChannelVideos: (args: FetchVideosArguments) => Promise <Video[]>

we are annotating the variable fetchChannelVideos that was declared not the function assigned. We are telling the TS compiler that this variable will have a function assigned to it with these types of arguments and return value.

While the right part of the = is the function assigned.

async ({
 shouldUseDefaultVideos = false,
 searchQuery
}) => {};

To annotate the function itself, we have to specify its arguments and their types and the return value as well.

So why didn't I annotate the function to assigned to fetchChannelVideos? Because again, TypeScript is smart.

Seeing that I assigned the function to a variable that was annotated, it is able to infer that the function will have the same argument names and types and the return value as well, otherwise it will give an error if I add or specify different argument names or return a different value.

*The function arguments and return value is inferred

However, if I’m exporting the function directly without assigning it to a variable then I have to annotate it like below.

export async function fetchChannelVideos({
 shouldUseDefaultVideos = false,
 searchQuery
}: FetchVideosArguments): Promise<Video[]> {
 // function body
}

Okay, now that we have an idea about the what, why and how of type annotations, there's one last question.

Where do we use types?

The answer is: everywhere. By using TypeScript, every data in your application will have a type associated with it whether you like it or not.

Closing Thoughts

I have only touched the basics so far but I’ve already seen how helpful it is, seeing the errors the compiler gives while writing the code instead of discovering them later when running the app saved me a lot of time.

What is an interface?

Interfaces are the core way in TypeScript to compose multiple type annotations into a single named annotation.

It’s a way of creating a new type that describes an object’s property names and their types. Remember the types that we covered before (number, boolean, string, etc.)? An interface is the same as those types.

Why should you care about interfaces?

For me, interfaces are one of the best features of the TypeScript language. Let me show you just how awesome it is.

Let's start with the following example.

You have a function called renderVideo which accepts video as a parameter in which you annotated with all the properties that should be present in a video object.

const video = {
  videoId: '',
  title: '',
  description: '',
  thumbnailURL: ''
};

const renderVideo = (video: { videoId: string; title: string; description: string; thumbnailURL: string }): void => {

}

Even though the parameter annotation is quite long, the code above is okay, the function parameter and return value was annotated correctly.

However, imagine if you added more functions which accepts a video.

const video = {
  videoId: '',
  title: '',
  description: '',
  thumbnailURL: ''
};

const renderVideo = (video: { videoId: string; title: string; description: string; thumbnailURL: string }): void => {

}

const getVideoSummary = (video: { videoId: string; title: string; description: string; thumbnailURL: string }): void => {

}

// another function has a `video` parameter
// another one
// and another one
// *shouts* "DJ Khaled"

Aaahhhh. I don't know about you but to me, the code above doesn't look good.

There are a lot of long annotations which are duplicated several times. The code is harder to read as well.

Now imagine again (we're imagining a lot here) that you need to do either one or more of the following changes to a video:

  • add the date it was published as a new property
  • rename thumbnailURL to imageURL
  • change the type of videoId from string to a number

Applying these new changes to the code above means you need to change all the video annotations in a lot of places.

That's a lot of work and we don't want that so how can we solve this?

Simple, by using an interface.

How to create an interface?

We can convert this long video annotation into an interface called Video.

To create an interface, use the interface keyword followed by a name that represents the object, in this case Video.

The name should start with a capital letter just like how we create React components and a set of curly braces where you declare all of the object's properties and methods and its types.

This is how you create the Video interface.

interface Video {
  videoId: string;
  title: string;
  description: string;
  thumbnailURL: string;
}

How do you use an interface?

Since an interface is a custom type, we can annotate it to a variable just like a normal type.

let item: Video;

By using the Video interface, we can rewrite the previous example to this:

interface Video {
  videoId: string;
  title: string;
  description: string;
  thumbnailURL: string;
}

const renderVideo = (video: Video): void => {};
const getVideoSummary = (video: Video): void => {};

// another function that uses a video
// another one
// and another one

const video = {
  videoId: "",
  title: "",
  description: "",
  thumbnailURL: ""
};

renderVideo(video);

This code looks wayyyyy better than the previous one right?

It looks cleaner and in one glance, we can see right away that the parameter is a type of Video.

Most of all, we can add, remove or rename a property or change a property type only in one place, the interface declaration. Awesome!

💡 If you use VS code, which I highly recommend you should, you can see all the property names and types of an interface by holding the ctrl/cmd key and hovering over the interface name.

Pretty cool right?

How are interfaces used in the app?

Aside from using the Video interface, I created another interface to represent how the Youtube API response is structured.

interface YoutubeVideo {
  id: { videoId: string };
  snippet: {
    publishedAt: string;
    title: string;
    description: string;
    thumbnails: { medium: { url: string }; default: { url: string } };
    channelTitle: string;
    channelId: string;
  };
}

interface YoutubeAPIResponse {
  data: { items: YoutubeVideo[] };
}

This interface is used in the fetchChannelVideos function.

export const fetchChannelVideos: (
  args: FetchVideosArguments
) => Promise<Video[]> = async ({
  shouldUseDefaultVideos = false,
  searchQuery
}) => {
 const requestURL = '';
 const response:YoutubeAPIResponse = await axios.get(requestURL);
};

Since an interface is a type, it means the TS compiler also does error-checking on the annotated data.

By creating an interface for the API response structure and annotating it to the variable, we made sure that we don't accidentally access a property that does not exist.

So if we try to access comments, which is not included in the API response, the TypeScript compiler will give us an error.

Writing Reusable Code with Interfaces

Now that we covered the what, why and how of interfaces, let's talk about how we can use it to write more reusable code.

Let's say you have a component that renders comments from a Youtube user and the channel.

First we write it like this:

interface User { name: string; avatar: string; }
interface Channel { name: string; avatar: string; }

const renderUserComment: (user: User, comment: string) => React.ReactNode = (
  user,
  comment
) => { /* return user comment */ };

const user = { name: "Ana", avatar: "ana.png" };
const channel = { name: "Tedx Talks", avatar: "tedxtalks.png" };

const renderChannelComment: (
  channel: Channel,
  comment: string
) => React.ReactNode = (channel, comment) => {/* return channel comment */};

renderUserComment(user, 'hello there');
renderChannelComment(channel, 'hi, thanks for watching');

There are two separate interfaces to represent a user and a channel as well as separate functions to render these comments even though they do the same thing, rendering a comment with the author.

How can we rewrite this to remove the duplicated code?

Although we can use a union type to indicate that the comment author can be a User or a Channel,

const renderComment: (author: User|Channel, comment: string) => React.ReactNode = (
  author,
  comment
) => { /* return comment */ };

In this scenario, it's better to create one interface to represent the two types of users.

Let's rewrite it using a more generic interface called Author.

interface Author { name: string; avatar: string; }

const renderComment: (author: Author, comment: string) => React.ReactNode = (
  author,
  comment
) => { /* return comment */};

const user = { name: "Ana", avatar: "ana.png", github: "analizapandac" };
const channel = { name: "Tedx Talks", avatar: "tedxtalks.png", channelId: "UCsT0YIqwnpJCM-mx7-gSA4Q" };

renderComment(user, 'hello there');
renderComment(channel, 'hi, thanks for watching');

Even though user and channel represents two different types of data, they satisfy the requirements to be considered an author which is a name and avatar so the renderComment function works for both objects.

The examples above are very basic but the point here is that we can use a generic interface to represent different objects which can work with different functions. This is one way to use interfaces to write more reusable code.

Closing Thoughts

Aside from learning about the what, why and how of interfaces, I hope that I have also shown you a glimpse of the power of interfaces and how you can use it to create amazing applications.

There's a lot more to interfaces especially on how to use it together with the other TypeScript features such as classes and write even better code.

I'll continue learning the more advanced TS concepts, creating new exciting projects using them and sharing them with you.

I hope you'll do the same 😊

Project Github Repo

Here's the source code of the app: https://github.com/analizapandac/tedflix

Getting Started with React, TypeScript and Webpack

Getting Started with React, TypeScript and Webpack

Here's my take on a starter project using React, TypeScript and Webpack.

Here's my take on a starter project using React, TypeScript and Webpack.

Getting Started

This will be a tutorial on getting a very bare-boned project up and running using React, TypeScript and Webpack. Alternatively you can skip directly to the source, available on my GitHub (complete with instructions) on getting the project up and running.

Setting up the project

  • Create a folder for your project.
mkdir your-folder-name && cd your-folder-name && npm init --yes

  • Install React and React-DOM as dependencies of the project.
npm install react && npm install react-dom

  • Under our devDependencies we need TypeScript.
npm install typescript --save-dev

  • Along with the Typings for React and React-DOM.
npm install @types/react --save-dev && npm install @types/react-dom --save-dev

  • Next we can initialize our TypeScript project. You should see a tsconfig.json file being created.
tsc --init

  • Open tsconfig.json, adding an include array after compilerOptions. This will tell TypeScript where to look for our code.
{
    "compilerOptions": {
    },
    "include":[ 
        "./src/**/*"
    ]
}

  • Now create a src folder, and inside create an App.ts file.
export class App
{
    constructor()
    {
        console.log("Hello app!");
    }
}

  • Test TypeScript is compiling by running tsc in the terminal. If successful, you should see an App.js file output to the src folder. Once it does, go ahead and delete the .js file once it appears.
Getting TypeScript and React working together

Now we have TypeScript compiling we can look at getting it to work with React files too.

  • In our tsconfig.json file, update yours to match the below.
{
    "compilerOptions": {
        "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
        "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
        "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
        "sourceMap": true, /* Generates corresponding '.map' file. */
        "outDir": "./dist/", /* Redirect output structure to the directory. */
        "removeComments": true, /* Do not emit comments to output. */
        "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
        "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
        "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
        "preserveConstEnums": true
    },
    "include": [
        "./src/**/*"
    ]
}

  • To test TypeScript will now pick up React files, we'll add a new file to our src folder called Main.tsx.
    import * as React from 'react';
    import { App } from './App';

    export interface IMainProps
    {
        app: App;
    }

    export class Main extends React.Component<IMainProps, {}>
    {
        public render(): JSX.Element
        {
            return (
                <>
                    <h1>Hello main</h1>
                </>
            );
        }
    }

  • Run tsc in your terminal, and you should now see a dist folder appear with a Main.js file, which means TypeScript is now also picking up React TypeScript files! (.tsx)
Adding Webpack to the mix

We now have TypeScript and React working together. What we need next, is for Webpack to bundle it all up and serve it in our browser.

First, we're going to install Webpack locally, as recommended by the official Webpack documentation.

  • Run the following in your terminal
    npm install webpack --save-dev && 
    npm install webpack-cli --save-dev && 
    npm install webpack-dev-server --save-dev && 
    npm install awesome-typescript-loader --save-dev && 
    npm install html-webpack-plugin --save-dev

  • We've now installed Webpack with four additional devDependencies

  • Let's go ahead and create our webpack.config.js file in the root of our project.

    const path = require('path');
    const HtmlWebPackPlugin = require('html-webpack-plugin');

    module.exports = {
        entry: {
            app: ['./src/App.ts'],
            vendor: ['react', 'react-dom']
        },
        output: {
            path: path.resolve(__dirname, 'dist'),
            filename: 'js/[name].bundle.js'
        },
        devtool: "source-map",
        resolve: {
            extensions: [".ts", ".tsx", ".js", ".jsx", ".json"]
        },
        module: {
            rules: [
                {
                    test: /\.tsx?$/,
                    loader: "awesome-typescript-loader"
                }
            ]
        },

        plugins: [
            new HtmlWebPackPlugin({
                template: "./src/index.html"
            })
        ]
    };

Things to note:
  • Our entry object contains a path to App.ts file we made earlier.
  • It also includes a vendor array, React and React-Dom are our only libraries, so we add these here. If you want to add additional libraries, you should add them to this so Webpack knows about it.
  • Our output object tells webpack where to bundle our app, which in this case is our dist folder.
  • Under module we've added our awesome-type-script-loader.
  • Under our plugin array, we've added our source index.html file using the HtmlWebPackPlugin. A minified html file will get placed in our dist folder along with the reference to our bundled up js files.

Next, create a new index.html file and add it to our src folder. Make sure there is <div id="app"></div> in yours. This is where our React app will look to render to.

    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>App</title>
    </head>

    <body>
        <div id="app"></div>
    </body>

    </html>

  • Open App.ts and add to the very bottom of the file new App();
export class App
{
    constructor()
    {
        console.log("Hello app!");
    }
}

new App();

  • Now in the root folder of the project, run in the terminal
node_modules/.bin/webpack-dev-server  --mode development

  • You should now have a successful build, with a webserver at http://localhost:8080/.
  • Inspecting the dev console (F12 > console), you should also see our log in the console outputting "Hello app!"
Making it more React

Now that we have React, TypeScript and Webpack all playing nicely, let's actually get some React rendering.

  • Create a Main.tsx file and paste in the below.
    import * as React from 'react';
    import { App } from './App';

    export interface IMainProps
    {
        app: App; // Reference to our App.ts class
    }

    export class Main extends React.Component<IMainProps, {}>
    {
        constructor(props: IMainProps)
        {
            super(props);
        }

        public render(): JSX.Element
        {
            return (
                <>
                    Main app
                </>
            );
        }
    }

  • Open App.ts file and paste in the below.
    import * as ReactDOM from 'react-dom';
    import * as React from 'react';
    import { Main } from './Main';

    export class App
    {
        constructor()
        {
            this.render();
        }

        private render(): void
        {
            ReactDOM.render(React.createElement(Main, { app: this }), document.getElementById("app"));
        }
    }

    new App();

Things to note here:
  • We're rendering our Main.tsx class as our main React UI.
  • We're passing in a reference to our App class as a react prop. There might be stuff we want to do or access in our App class at a later date.
  • We're passing in our app id element we added to our index.html file earlier. This will be where React will render.

Now if we go back to our browser, we should see "Main app" on our page. Your browser should have auto-reloaded since we changed some code.
React has now landed on our webpage.

Tidying up and publishing

Typing in ./node_modules/.bin/webpack-dev-server --mode development just to run the dev server everytime isn't great.
We can change this into a more friendly node command.

Open up our package.json file and update the scripts object so it looks like below:

{
    "scripts": {
        "dev": "webpack-dev-server --mode development",
        "build": "webpack --mode production"
    }
}

We can now run our above commands in the terminal:

  • Running npm run dev is now doing what we were typing earlier: ./node_modules/.bin/webpack-dev-server --mode development

  • Running npm run build will tell webpack to compile our app for production. It will essentially minify everything and bundle it into our
    dist folder, ready to upload to the web.

Source code

And that's it! You now have TypeScript, React and Webpack playing nice.

As mentioned at the start, you can find the source code on my GitHub.
It also includes a very basic look at React components and adding them to our Main.tsx file.

Feel free to give it a star if it's helped you out.