Categories
API's Full Stack Development Mobile Development Platforms

Swift Stack: Be a Full Stack Swift Developer (Part One)

Overview

Swift as a language has grown from infancy quite rapidly since its release in 2014. The programming language, initially a proprietary language, was released to the community as an open-source project with version 2.2 in late December 2015.

At the heart of the open-source language is the community’s goal for the language:

To create the best available language for uses ranging from systems programming to mobile and desktop apps, scaling up to cloud services.

Swift Language, source: http://swift.org/about/

So what’s the big deal?

Initially Swift only supported the development of desktop and native mobile applications for devices running iOS and Mac OS. However, over the years, support for Unix based architecture and more recently official support for windows in Swift 5.3 (latest at time of writing), has enabled the development of applications and solutions in Swift to reach a much wider audience and developer base.

If you would like to read more on the port of Swift to Windows, I suggest reading through their blog post on the official Swift site.

Why Swift?

So you may be asking, why should I be using Swift for my server-side development? Why is it any better than Java, C#, NodeJS (JS/TS), Python?

Swift has many advantages when developing, some of these include uninitialised variable prevention, overflow checking, and automated memory management thanks to ARC. As well as this, the language design and syntax promote swift (fast) development with high maintainability and readability. Swift being a young language, it still has a lot of ways to go and will continuously see improvements to its performance and feature base.

You can read up on the language itself and look into the pros and cons. But one of the main advantages of building in Swift for server-side and full-stack is the reusability of code across your mobile, web, and server-side developments, this allows for the sharing of business logic, models, and validation across your project. This also enables language familiarity across your codebase to promote work in a cross-functional development team.

Another big advantage is if you are already an experienced iOS developer, the transition to developing APIs and backend services for your application is seamless, with no need to learn a new language.

The transition from other languages such as JavaScript, TypeScript, Kotlin, etc. is also very simple as the language is designed for fast development and it is easy to learn, sharing a lot in common with those aforementioned.

Vapor

Vapor is a web framework built for server-side Swift development. It’s built on Apple’s SwiftNIO: a non-blocking, event-driven architecture written in Swift which encourages type-safe, expressive, and protocol-oriented development.

We will be covering Vapor in this tutorial. Vapor is one of the most used web frameworks for Swift server-side development. However, other options do exist but a lot of them are no longer supported or have died out. If you wish to explore them as well here are a couple: Kitura (IBM no longer supports this) and Perfect.

Prerequisites

Before we dive in and get our hands dirty, you will need to have the following setup on your system:

  • Swift > 5.2
  • IDE or Text Editor with Swift support (optional)

Setting up Vapor

We will be installing Vapor in the next section of the tutorial using Vapor Toolbox for CLI shortcuts, which is only available for macOS and Linux. If you are on Windows you can still use the Vapor framework but will have to manually set up your project. You can download the API Template from Vapor’s GitHub repository.

To install the Vapor Toolbox, you can use brew or apt-get.

# macOS
brew install vapor 

# linux
git clone https://github.com/vapor/toolbox.git
cd toolbox
git checkout <desired version>
make install

# commands
vapor --help

Now that we have Vapor installed, we can use the vapor command. Let’s start by creating our project.

Out of the box, Vapor supports the use of database drivers, we will not be using these in the tutorial so if prompted when creating your new project to install any extra packages you can answer no.

vapor new shopApi
cd shopApi
vapor build

We are creating a new project here named shopApi. In this tutorial, we will be creating a simple shopping app which will display a list of products available to a user.

If you would like to read more about the differences between dynamic linking and static linking you can read further at https://swift.org/package-manager/.

The rest of the tutorial will focus on development using the macOS environment. All code snippets and Vapor CLI commands will carry across to Linux as well; however, running the application will be done through the terminal instead of through Xcode.

Running our Swift application from the command line:

vapor build && vapor run

If you are on macOS you can run:

vapor xcode

This will open the project in Xcode where you can use the normal build and run buttons you are familiar with for mobile development. You can also set breakpoints inside your code to debug your application.

Building your first route

It’s time to get our hands dirty. Let’s start by creating our first route for our basic shopApi.

Endpoints

First, let us define some constants for our endpoints. Create a new Constants.swift file with the following constants.

// Constants.swift

import Vapor

public enum Constants {
    
    public enum Endpoints: String {
        // MARK:- Shop Endpoints
        case products = "products"
        case singleProduct = "productId"
       
        var path: PathComponent {
            return PathComponent(
                stringLiteral: self.rawValue
            )
        }
    }
}

Here we define two constants for the routes we will be using. You will see later how we use the “:productId” to provide a path template for an endpoint to retrieve a single product.

Routes

Now open the routes.swift file and replace the existing routes with a new route for the two endpoints: products and products/:productId.

// routes.swift

func routes(_ app: Application) throws {
    app.get(Constants.Endpoints.products.path) { 
        req -> String in
        
        return "All Products"
    }

    app.get(Constants.Endpoints.products.path, 
            Constants.Endpoints.singleProduct.path) { 
        req -> String in
        
        return "Single Product"
    }
}

When we define the endpoint for products/:productId, Vapor uses the colon (:) as an identifier for a URL path parameter. We can access this inside the function using the following:

let param = req.parameters.get("productId")

or

# Using type casting
let param = req.parameters.get("productId", as: Int.self)

Now if we run the project (Run in Xcode) you should see the following:

vapor build && vapor run
  
    Building project...
    [8/8] Linking Run
    Project built.
    [ NOTICE ] Server starting on http://127.0.0.1:8080

Navigate in a browser or using Postman to http://127.0.0.1:8080/products you should see the server respond with “All products”.

Congratulations, you now have your first endpoints setup using Vapor.

Models and business logic

Let’s now explore the reusability of models and business logic across our server-side and application codebase. We discussed earlier that this is one of the main advantages of using Swift across your entire stack.

Create a new file called Product.swift in the Models folder (create this if it does not exist).

In a real-world scenario, this would be developed as part of a common framework or module and imported into the project for code re-usability across your Swift Stack. For simplicity in this tutorial, we will not cover creating a Swift package for use in your project.

// Product.swift

import Foundation

struct Product: Codable {
    // MARK:- Properties
    private var id: Int
    private var name: String
    private var price: Double
    private var description: String
    
    // MARK:- Lifecycle
    init(id: Int, name: String, 
         price: Double, description: String) {
        self.id = id
        self.name = name
        self.price = price
        self.description = description
    }
    
    // MARK:- Codeable Methods
    func asJsonString() -> String {
        let codedProduct = try! JSONEncoder().encode(self)
        return String(data: codedProduct, encoding: .utf8)!
    }
}

In this product model file, we create a basic data structure for our products and conform it to the Codable type. This allows us to serialise our object both in our server-side and client-side applications.

We also have an instance helper method here to serialise our codeable product to a string for passing as a response body.

This product model will now be useable in both our server-side and client-side applications, and any changes that are made to this data structure in our common framework will be reflected across both applications, reducing the development effort when updating and ensuring alignment between client and server.

Services

Services in Vapor can be registered as a part of the application to act as the business logic layer. Now that we have created our model for the shop, let’s create a service which will serve some dummy products for our shop.

Create a new file called ProductService.swift inside a Services folder (Create this folder if it does not exist).

// ProductService.swift

import Foundation
import Vapor

class ProductService {
    var products: [Product] = []
    
    //
    // Initialise our products.
    // This is a mock that returns hard coded products
    //
    init() {
        // Create some dummy products
        let toucan = Product(
            id: 1,
            name: "Toucan",
            price: 50.00,
            description: "Famous bird of the zoo"
        )
        
        let elephant = Product(
            id: 2,
            name: "Elephant",
            price: 85.00,
            description: "Large creature from Africa"
        )
        
        let giraffe = Product(
            id: 3,
            name: "Giraffe",
            price: 65.00,
            description: "Long necked creature"
        )
        
        // Add them to our products array
        products.append(toucan)
        products.append(elephant)
        products.append(giraffe)
    }
    
    //
    // Filter our products array and get by matching id
    //
    func getProductById(id: Int) -> Product? {
        return products.first(where: { $0.id == id })
    }
    
    //
    // Return all products
    //
    func getProducts() -> [Product] {
        return products
    }
}

In this simple service, we are instantiating 3 products and storing them in our products array. This logic in practice would be replaced with a database implementation to store and access our products. However, for now, we are just hardcoding the values stored in our shop.

There are two methods in this service, one which we will use to return all the products that our store contains, and the other to return a product by its ID. These two methods match up with our current routes.


Registering our service

Now to access our service from our routes or controllers, we must register them in the Vapor application. To do this let’s add this extension to the bottom of our ProductService.swift file.

// MARK:- Services implementation
extension Application {
    
    //
    // Register our product service with the Vapor     
    // application. 
    // 
    var productService: ProductService {
        .init()
    }
}

In Vapor 4, you now register your services as extensions of either the Application or Request objects. This exposes the services as properties and allows for easier use in our routes and controllers.

This allows us to use our ProductService methods by calling:

app.productService.getProducts()

Codable Extension

Lastly, before we hook everything up and can start responding with products for our API, we must write an extension to serialise the list of our products to a JSON string. Open the Product.swift file and at the end of the file add the following extension.

// Product.swift

struct Product: Codable {
...
...
}

extension Array {
    typealias CodableArray = [Product]
    
    //
    // Encode our array as a Json string.
    //
    func codableArrayAsJsonString() -> String {
        if let array = self as? CodableArray {
            let codedArray = try! 
                JSONEncoder().encode(array)

            return String(
                data: codedArray, 
                encoding: .utf8
           )!
        }
        
        // This is where we can add some error handling,
        // But for now we will just return blank
        return ""
    }
}

This extension allows us to encode a list of our products to a JSON string for use as the body in a response object.

Putting it all together

Now that we have built our business logic and models, we can now start responding to our client with the products our shop offers. Let’s open the routes.swift file again and modify our /products route.

All Products Route

//
// Register an endpoint for /products
//
app.get(Constants.Endpoints.products.path) {
    req -> Response in
        
    // Call our product service to get our products
    let products = app.productService.getProducts()
        
    // Return a serialised list of products
    return .init(status: .ok,
             version: req.version,
             headers: [
               "Content-Type": 
               "application/json"
             ],
             body: 
               .init(string:
                  products.codableArrayAsJsonString()
               ))
}

Here we are calling our service that we registered earlier on our application object, and retrieving the list of products our shop offers.

We are then creating a response object which will return the encoded JSON list of our products in the body using our extension: codableArrayAsJsonString().

Single Product Route

Let’s add modify our final route, which takes in a url-path parameter for the productId and returns the product if it is found.

//
// Register an endpoint for /products/:productId
//
app.get(Constants.Endpoints.products.path, 
        ":\(Constants.Endpoints.singleProduct.path)"
    ) {
    req -> Response in
        
    // Get our productId from the url-path parameter
    let productId = req.parameters.get(
        Constants.Endpoints.singleProduct.rawValue,
        as: Int.self
    ) ?? 0
        
    // Call our product service to get the product by id
    if let product =
       app.productService.getProductById(id: productId) {
            return .init(status: .ok,
                     version: req.version,
                     headers: [
                       "Content-Type": 
                       "application/json"],
                     body: 
                      .init(
                        string: product.asJsonString()
                      ))
    }
        
    // Return no product found
    return .init(status: .ok,
             version: req.version,
             headers: [
               "Content-Type": 
               "application/json"
             ],
             body: .init(string: "No product found."))
    }

Here we get the productId from the url-path of the request and use it to call our service with the .getProductById() method. This returns a single product matching the ID. We then encode the product as a JSON String and set it as the response body.

If no product is found we return a 404 Product not found.

Running the application

Finally, let’s run our application to see our API in its finished state.

If you call the http://127.0.0.1:8080/products endpoint you should now see the following response:

[
   {
      "id":1,
      "name":"Toucan",
      "price":50,
      "description":"Famous bird of the zoo"
   },
   {
      "id":2,
      "name":"Elephant",
      "price":85,
      "description":"Large creature from Africa"
   },
   {
      "id":3,
      "name":"Giraffe",
      "price":65,
      "description":"Long necked creature"
   }
]

Now, now if you call the single product endpoint with a product id

http://127.0.0.1:8080/products/:productId

For example: /products/1 you should see the following response:

{
   "id":1,
   "name":"Toucan",
   "price":50,
   "description":"Famous bird of the zoo"
}

Congratulations, you now have a simple products API built entirely in Swift.


Next: Swift Stack: Become a Full Stack Swift Developer (Part Two)
In the next part, we will look at deploying our Swift server into the cloud using a CI/CD pipeline. We will also be looking at how to write tests and how we can run these as a part of our pipeline.

The source code for this tutorial is available at: https://github.com/justinwilkin/server-side-swift-tutorial-part-1

Categories
Angular Mobile Development Web Development

Introduction to WebGL Using Angular- How to Set Up a Scene (Part 1)

Overview

WebGL has to be one of the most under-used JavaScript APIs within modern web browsers.

It offers rendering interactive 2D and 3D graphics and is fully integrated with other web standards, allowing GPU-accelerated usage of physics and image processing effects as part of the web page canvas (Wikipedia, 2020).

In this article, we’re going to setup WebGL within a typical Angular app by utilising the HTML5 canvas element.

Prerequisites

Before starting, its worthwhile to ensure your system is setup with the following:

  • Nodejs is installed
  • You have setup a new or existing Angular app
  • You are using a modern web browser (Chrome 56+, Firefox 51+, Opera 43+, Edge 10240+)

WebGL Fundamentals

There’s honestly a lot to take in regards to the fundamentals of WebGL (and more specifically OpenGL). It does mean that you’ll need to have some basic understanding of linear algebra and 2d/3d rendering in general. WebGL Fundamentals does a great job at providing an introduction to WebGL fundamentals and I’ll be referencing their documentation as we step through setting up our Angular app to use WebGL.

Before going any further, its important that you understand the following at a minimum.

WebGL is not a 3D API. You can’t just use it to instantly render objects and models and get them to do some awesome magic.

WebGL is just a rasterization engine. It draws points, lines and triangles based on the code you supply.

If you want WebGL to do anything else, its up to you to write code that uses points, lines and triangles to accomplish the task you want.

WebGL runs on the GPU and requires that you provide code that runs on the GPU.
The code that we need to provide is in the form of pairs of functions.

They are known as:

  • a vertex shader
    • responsible for computing vertex positions – based on the positions, WebGL can then rasterize primitives including points, lines, or triangles.
  • a fragment shader
    • when primitives are being rasterized, WebGL calls the fragment shader to compute a colour for each pixel of the primitive that’s currently being drawn.

Each shader is written in GLSL which is a strictly typed C/C++ like language.
When a vertex and fragment shader are combined, they’re collectively known as a program.

Nearly all of the entire WebGL API is about setting up state for these pairs of functions to run. For each thing you want to draw, you setup a bunch of state then execute a pair of functions by calling gl.drawArrays or gl.drawElements which executes your shaders on the GPU.

Any data you want those functions to have access to, must be provided to the GPU. There are 4 ways a shader can receive data.

  • Attributes and buffers
    • Buffers are arrays of binary data you upload to the GPU. Usually buffers contain things like positions, normals, texture coordinates, vertex colours, etc although you’re free to put anything you want in them.
    • Attributes are used to specify how to pull data out of your buffers and provide them to your vertex shader. For example you might put positions in a buffer as three 32bit floats per position. You would tell a particular attribute which buffer to pull the positions out of, what type of data it should pull out (3 component 32 bit floating point numbers), what offset in the buffer the positions start, and how many bytes to get from one position to the next.
    • Buffers are not random access. Instead a vertex shader is executed a specified number of times. Each time it’s executed the next value from each specified buffer is pulled out and assigned to an attribute.
  • Uniforms
    • Uniforms are effectively global variables you set before you execute your shader program.
  • Textures
    • Textures are arrays of data you can randomly access in your shader program. The most common thing to put in a texture is image data but textures are just data and can just as easily contain something other than colours.
  • Varyings
    • Varyings are a way for a vertex shader to pass data to a fragment shader. Depending on what is being rendered, points, lines, or triangles, the values set on a varying by a vertex shader will be interpolated while executing the fragment shader.

(WebGL Fundamentals, 2015).

I’m glossing over a lot of technical detail here, but if you really want to know more, head over to WebGL Fundamentals lessons for more info.

Setting up a playground

Let’s set up a playground so we have something that we can use in order to continue setting up WebGL.

First, create a component. You can create one by executing the following command within your Angular root (src) directory. I’ve gone ahead and named mine scene.

E.g. ng generate component scene

PS X:\...\toucan-webgl> ng generate component scene
CREATE src/app/scene/scene.component.html (20 bytes)
CREATE src/app/scene/scene.component.spec.ts (619 bytes)
CREATE src/app/scene/scene.component.ts (272 bytes)
CREATE src/app/scene/scene.component.scss (0 bytes)
PS X:\...\toucan-webgl>

Let’s also create a service with the component and call it WebGL too.

E.g. ng generate service scene/services/webGL

PS X:\...\toucan-webgl> ng generate service scene/services/webGL
CREATE src/app/scene/services/web-gl.service.spec.ts (352 bytes)
CREATE src/app/scene/services/web-gl.service.ts (134 bytes)
PS X:\...\toucan-webgl>

If you’re using a new Angular app, hopefully you’ve already configured it to use App Routing. If you haven’t, follow the next couple of steps.

ng generate module app-routing --flat --module=app

You’ll now have an app-routing.module.ts file, if you haven’t got one already.

Update the contents of the file with the following:

import { Routes, RouterModule } from "@angular/router";
import { SceneComponent } from "./scene/scene.component";
const routes: Routes = [{ path: "", component: SceneComponent }];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

This will ensure that on app load, it’ll display the SceneComponent first.

Next, add the WebGLService to the SceneComponent‘s constructor like so:

import { Component, OnInit } from "@angular/core";
import { WebGLService } from "./services/web-gl.service";
@Component({
  selector: "app-scene",
  templateUrl: "./scene.component.html",
  styleUrls: ["./scene.component.scss"],
})
export class SceneComponent implements OnInit {
  // *** Update constructor here ***
  constructor(private webglService: WebGLService) {}
  ngOnInit(): void {}
}

Finally, run ng serve and check to see if the Angular app is running and displaying the SceneComponent.
It should look like this:

Now, lets move onto adding a WebGL context.

Setting up the WebGL context

Setting up the WebGL context is a little bit involved but once we get the foundation going we can then proceed to start getting something on the screen.

Let’s start by opening up scene.component.html and add a HTML5 canvas element.

<div class="scene">
  <canvas #sceneCanvas>
    Your browser doesn't appear to support the
    <code><canvas></code> element.
  </canvas>
</div>

Open up scene.component.scss (or equivalent) and add in the following styles:

.scene {
  height: 100%;
  width: 100%;
}
.scene canvas {
  height: 100%;
  width: 100%;
  border-style: solid;
  border-width: 1px;
  border-color: black;
}

The following css should just make sure the canvas element extends to the size of the browser window. I just added some border styling so you can explicitly see it for yourself.

TIP: If you want, you can also update the global styles.scss so all content expands to the height of the window respectively.

styles.scss

/* You can add global styles to this file, and also import other style files */
html,
body {
  height: 99%;
}

We’ll now embark on doing the following:

  1. Resolving the canvas element in typescript via the #canvas id
  2. Binding the canvas element to a WebGL rendering context
  3. Initialize the WebGL rendering canvas

Resolving the canvas element

Open scene.component.ts and add the following property:

@ViewChild('sceneCanvas') private canvas: HTMLCanvasElement;

Update your the SceneComponent class to implement AfterViewInit, we’ll need to hook into this lifecycle hook to continue setting up the WebGL canvas.

Add in the following guard to the ngAfterViewInit method to ensure that we actually have the canvas element before attempting to bind it:

if (!this.canvas) {
  alert("canvas not supplied! cannot bind WebGL context!");
  return;
}

NOTE: If the alert is hit, it’s due to the fact that the ElementRef ID you’re using does match the one defined in HTML and the TS class. You need to ensure they match.

Your component implementation should now look like this:

import { AfterViewInit, Component, OnInit, ViewChild } from "@angular/core";
import { WebGLService } from "./services/web-gl.service";
@Component({
  selector: "app-scene",
  templateUrl: "./scene.component.html",
  styleUrls: ["./scene.component.scss"],
})
export class SceneComponent implements OnInit, AfterViewInit {
  @ViewChild("sceneCanvas") private canvas: HTMLCanvasElement;
  constructor(private webglService: WebGLService) {}
  ngAfterViewInit(): void {
    if (!this.canvas) {
      alert("canvas not supplied! cannot bind WebGL context!");
      return;
    }
  }
  ngOnInit(): void {}
}

Binding the canvas element to a WebGL rendering context

Open up the web-gl.service.ts file.

Create a method called initialiseWebGLContext with a parameter canvas: HTMLCanvasElement.

initialiseWebGLContext(canvas: HTMLCanvasElement) {
}

Go back to scene.component.ts and add in the following line after the guard check in ngAfterViewInit.

ngAfterViewInit(): void {
  if (!this.canvas) {
      alert('canvas not supplied! cannot bind WebGL context!');
      return;
  }
  this.webglService.initialiseWebGLContext(this.canvas.nativeElement);
}

Now, back in web-gl.service.ts, lets retrieve a WebGL context from the canvas’s native element and reference it to a property that we’ll call gl.

private _renderingContext: RenderingContext;
private get gl(): WebGLRenderingContext {
  return this._renderingContext as WebGLRenderingContext;
}
constructor() {}
initialiseWebGLContext(canvas: HTMLCanvasElement) {
  // Try to grab the standard context. If it fails, fallback to experimental.
  this._renderingContext = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
  // If we don't have a GL context, give up now... only continue if WebGL is available and working...
  if (!this.gl) {
      alert('Unable to initialize WebGL. Your browser may not support it.');
      return;
  }
}

Once we’ve retrieved the WebGLRenderingContext, we can then set the WebGL canvas’s height and width, and then finally proceed to initialise the WebGL canvas.

Lets add two methods which do that I described above:

setWebGLCanvasDimensions(canvas: HTMLCanvasElement) {
  // set width and height based on canvas width and height - good practice to use clientWidth and clientHeight
  this.gl.canvas.width = canvas.clientWidth;
  this.gl.canvas.height = canvas.clientHeight;
}
initialiseWebGLCanvas() {
  // Set clear colour to black, fully opaque
  this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
  // Enable depth testing
  this.gl.enable(this.gl.DEPTH_TEST);
  // Near things obscure far things
  this.gl.depthFunc(this.gl.LEQUAL);
  // Clear the colour as well as the depth buffer.
  this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
}

Now finally call them at the end of the initialiseWebGLContext method.

initialiseWebGLContext(canvas: HTMLCanvasElement) {
  // Try to grab the standard context. If it fails, fallback to experimental.
  this._renderingContext =
    canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
  // If we don't have a GL context, give up now... only continue if WebGL is available and working...
  if (!this.gl) {
    alert('Unable to initialize WebGL. Your browser may not support it.');
    return;
  }
  // *** set width, height and initialise the webgl canvas ***
  this.setWebGLCanvasDimensions(canvas);
  this.initialiseWebGLCanvas();
}

Run the app again, you should now see that the canvas is entirely black.

This shows that we’ve successfully initialised the WebGL context.

Thats it for part 1!

Next: Introduction to WebGL using Angular – Part 2 – Setting up shaders and a triangle

In part 2, we’ll proceed to add in shaders and start setting up content to render on screen!

Stay tuned!

The source code for this tutorial is available at https://gitlab.com/MikeHewett/intro-webgl-part-1.git

References