Workshop
In a nutshell
Communities
Social Developer
Recognitions
Senior Product Manager Cosmosdb
GDE,MCT and former MVP
Top stackoverflow contributor
@sajeetharan
@kokkisajee
@sajeetharan
@sajeetharan
What Devfest/Angular has given me
2010
2015
2019
2018
2020
Attended first Devfest SL 2012
Started with angular, became the top angularjs answerer on SO
Became the first google dev expert in the country
Started NG-Srilanka, First Angular Team meet at San Francisco
Book on Angular Projects,Top 4 contributors on Angular
2022
To produce 2 more GDEs from SL
👨🎨 Front-End developers ?
👨💻 Back-End developers ?
🦸♂️ Something else ? something in between ?
☁️ Cloud developers ?
👍A “Beginner workshop"
👍It won’t teach you everything, But it will help you learn what you need to know to get started
👍Be ready to ask questions
👍Celebrate together!
Introduction to JAMstack, Angular and why it's important
Graphql and it's core concepts
Get our hands dirty
How to Build JAM stack (Angular) App with Graphql
@kokkisajee
@kokkisajee
Server
1. Request
2. HTML
@kokkisajee
@kokkisajee
Backend
1. Request
4. Dynamically generated HTML
DB
2. Query
3. Results
But: "Click and wait" for every page
@kokkisajee
Server
1. Request
2. HTML
3. AJAX Request
4. XML (oder JSON)
@kokkisajee
Linux
Apache
MySQL
PHP
Mongo
Express
Angular
Node
Mongo
Express
React
Node
Traditional Work Flow
WebServer
Client
AppServer
DB
@kokkisajee
No full reload on further navigation
Better UX compared to "click and wait"
Slow Time to first paint (due to parsing)
White screen / flash of content
Javascript generates HTML -> bad SEO
mediocre UX for initial page load
@kokkisajee
@kokkisajee
Traditional SSR
"Old" Static pages
Single Page Apps
Server Side Rendering
JAMstack
CDN
Client
DB
@kokkisajee
@kokkisajee
avascript
PIs
arkup
@kokkisajee
Static, won't be modified by server
Generated on build-time (before (re-)deploying)
Usually compiled/built via Static Site Generator (SSG)
@kokkisajee
<app-make-meme *ngIf="imgUrl" [imageUrl]="imgUrl"></app-make-meme>
@kokkisajee
Responsible for the website's dynamics
Only on client-side
Independent of frameworks or libraries
@kokkisajee
Used for interacting with the website (comments, forms...)
Called through Javascript and HTTPS
Good fit: Microservices, Serverless Functions or similar
@kokkisajee
import { promisify } from 'util'
import sendmail from 'sendmail'
const shouldSend = process.env.SEND_MAIL
exports.handler = async (event) => {
if (event.httpMethod !== 'POST') {
return { statusCode: 405, body: JSON.stringify({ 'error': 'Method not allowed' }) }
}
try {
const params = JSON.parse(event.body)
const attributes = ['name', 'email', 'msg']
const sanitizedAttributes = attributes.map(n => validateAndSanitize(n, params[n]))
const someInvalid = sanitizedAttributes.some(r => !r)
if (someInvalid) {
return { statusCode: 422, body: JSON.stringify({ 'error': 'Ugh.. That looks unprocessable!' }) }
}
const sendMail = (name, email, msg) => pSendMail({
from: email,
to: process.env.MAIL_TO,
subject: 'New contact form message',
text: msg
})
try {
await sendMail(...sanitizedAttributes)
return { statusCode: 200, body: JSON.stringify({ 'message': 'OH YEAH' }) }
} catch (e) { /* */ }
} catch (_) { /* */}
}
@kokkisajee
Create your page
via SSG
Upload your code
via Git
Build your page with your SSG
Deploy the static Files via CDN or similar
Done!
@kokkisajee
@kokkisajee
Performance
Simplicity
Security
Cost
@kokkisajee
Only static HTML files, no .php / .py / ...
You don't need a server*
No difficult deploy pipelines anymore!
*hosting via. GitHub pages oder Netlify
@kokkisajee
No content generated "on the fly". Static files!
API endpoints are small and ideally isolated
Good practice: "Content API" only available at build time
@kokkisajee
@kokkisajee
Improved Time to first Byte due to CDN
Easy Cache Invalidation
Quick Response Time as it is just HTML
Ideal for SEO
@kokkisajee
No server, no server fees!
Free Hosting (via Netlify or GH Pages)
Serverless APIs are billed by usage
@kokkisajee
@kokkisajee
@kokkisajee
@kokkisajee
@kokkisajee
@kokkisajee
@kokkisajee
@kokkisajee
@kokkisajee
Sajee
Sanga
Mahela
"CRUD pls"
Sajee
Sanga
"small change pls"
release
"small change pls"
"small change pls"
"small change pls"
@kokkisajee
@kokkisajee
http://facebook.github.io/graphql/draft/
@kokkisajee
Sajee
Sanga
Mahela
"CRUD pls"
Sajee
Sanga
"small change pls"
release
"small change pls"
"small change pls"
"small change pls"
@kokkisajee
Sajee
Sanga
Mahela
GraphQL
@kokkisajee
@kokkisajee
QUERY
MUTATION
SUBSCRIPTION
Sanga Mahela Quit Cricket
@kokkisajee
@kokkisajee
Get person data using an ID
{
"name": "Maithiri Sirisena",
"birthYear": "41.9BBY",
"CourseId": 1
"filmIds": [1, 2, 3, 6]
}
GET /participants/{id}
GET /participant/4
Then to read the course’s name, we ask:
GET /participants/4
GET /participants/1
And to read the attended session titles, we ask:
GET /participants/4
GET /courses/1
GET /sessions/1
GET /sessions/2
GET /sessions/3
GET /sessions/6
Issue#1 Multiple round trips
Issue#2 Over-fetching of data
While requesting nested data, we often have to make several round trips or define an entirely newend point
Wouldn't it be nice if client receives only the data that it requires?
Issue#3 Absence of Self-Documentation
The onus lies with the developer to document the API and allow exploration and discovery of your APIs.
GET /graphql?query=
{
Participant(ID: 4) {
name,
alias,
comments {
description
},
sessions {
title
}
}
}
{
"data": {
"person": {
"name": "Ranil",
"alias":Wick
"Comments": {
"name": ""
},
"Sessions": [
{ "title": "Database Internals" },
{ "title": "Graphql" }
}
}
}
{
"me": {
"name": "Sajeetharan"
}
}
{
me {
name
}
}
Basic concepts
@kokkisajee
@kokkisajee
Query
Mutation
Subscription
Basic concepts
@kokkisajee
query GET_MAIN_DATA {
me {
}
}
name
bio
avatarLg: avatar_url(size: 168)
avatarSm: avatar_url(size: 40)
socials {
url
}
photos(limit: 3) {
src
}
friends(limit: 3) {
name
avatar_url(size: 100)
}
Query : Example
@kokkisajee
Query
Mutation
Subscription
Basic concepts
@kokkisajee
mutation CREATE_POST($value: String!) {
createPost(value: $value) {
}
}
author {
name
avatar_url(size: 40)
}
body
id
date
...
@kokkisajee
Query
Mutation
Subscription
Basic concepts
@kokkisajee
subscription FEED_UPDATES {
postAdded {
}
}
author {
name
avatar_url(size: 40)
}
body
id
date
...
@kokkisajee
query getUser($withFriends: Boolean) {
me {
name
friends @include(if: $withFriends) {
name
}
}
}
Basic concepts : Directive
@kokkisajee
Basic concepts
schema: {
query: Query
mutation: Mutation
subscription: Subscription
}
@kokkisajee
Query: {
user(obj, args, context, info) {
return context.db.loadUserByID(args.id)
}
}
User: {
name(obj, args, context, info) {
return obj.name
}
}
@kokkisajee
An in-browser IDE for exploring GraphQL.
@kokkisajee
Command line tool for common GraphQL development workflows
https://github.com/graphql-cli/graphql-cli
Command | Description |
---|---|
graphql create [directory] | Bootstrap a new GraphQL project |
graphql get-schema | Download schema from endpoint |
graphql schema-status | Show source & timestamp of local schema |
graphql query <file> | Run query/mutation |
graphql diff | Show a diff between two schemas |
@kokkisajee
Download
https://nodejs.org/en/download/
https://code.visualstudio.com/
Install
npm install -g @angular/cli@latest | @angular/cli@14.0.10
@kokkisajee
# Create project folder
mkdir devfest-todo && cd devfest-todo
# Create sub-folder for our server
mkdir devfest-todo-seerv && cd devfest-todo-server
@kokkisajee
npm init
# Install typescript, nodemon and node-ts
npm i typescript nodemon ts-node --save-dev
# Initialize TypeScript project
npx tsc --init --rootDir src --outDir dist --lib dom,es6 --module commonjs --removeComments
@kokkisajee
Apollo is a platform for building a data graph, a communication layer that seamlessly connects application clients (JAM stack and Mobile) to back-end services.
@kokkisajee
# Apollo Platform and Server dependencies
npm i apollo-server-express@^2 cors express graphql http ncp uuid --save
# Typing files for Node.js and dependencies
npm i @types/express @types/graphql @types/uuidv4 @types/node graphql-import graphql-import-node graphql-tools@4.x --save-dev
@kokkisajee
// package.json // ... "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon --exec npx ts-node ./src/index.ts -e ts,graphql" }, // ...
// package.json // ... "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon --exec npx ts-node ./src/index.ts -e ts,graphql" }, // ...
@kokkisajee
// src/index.ts
import express from 'express';
import {ApolloServer} from 'apollo-server-express';
import {createServer} from 'http';
import cors from 'cors';
import {schema} from './schema';
const host = '0.0.0.0';
const port = 8080;
const app = express();
const server = new ApolloServer({schema});
server.applyMiddleware({app, path: '/graphql'});
const httpServer = createServer(app);
httpServer.listen(
{host, port},
(): void => console.log(`\n🚀 GraphQL is now running on http://localhost:${port}/graphql`)
);
@kokkisajee
// src/schema.graphql
type Query {
helloWorld: String!
}
@kokkisajee
// src/schema.ts
import 'graphql-import-node';
import * as typeDefs from './schema.graphql';
import {makeExecutableSchema} from 'graphql-tools';
import {GraphQLSchema} from 'graphql';
import {IResolvers} from 'graphql-tools';
export const resolvers: IResolvers = {
Query: {
helloWorld() {
return `👋 Hello world! 👋`;
}
}
};
export const schema: GraphQLSchema = makeExecutableSchema({
typeDefs,
resolvers
});
@kokkisajee
@kokkisajee
A framework for web front-end app
A platform for integrated development
An ecosystem for developers
If you are new to Angular Watch Angular Zero to Hero From here
(1) Angular Zero to Hero by Sajeetharan Sinnathurai of Microsoft | Quarantine Tech Talk - YouTube
@kokkisajee
Building Blocks of Angular
# Create todo app (in the graphql-todo folder!)
ng new devfest-todo-app
# ? Would you like to add Angular routing? No
# ? Which stylesheet format would you like to use? CSS
# Enter folder and serve app
cd devfest-todo-app
#start the application
npm start
@kokkisajee
# Using the CLI we can add ng addable libraries
ng add @angular/material@14.0
# ? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink
# ? Set up HammerJS for gesture recognition? Yes
# ? Set up browser animations for Angular Material? Yes
@kokkisajee
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule} from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import {MatListModule} from "@angular/material/list";
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import {MatFormFieldModule} from "@angular/material/form-field";
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule } from '@angular/common/http';
import { MatInputModule } from '@angular/material/input';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
MatButtonModule,
MatMenuModule,
MatToolbarModule,
MatIconModule,
MatCardModule,
MatListModule,
MatFormFieldModule,
MatCheckboxModule,
MatInputModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
@kokkisajee
# Install Apollo Angular
ng add apollo-angular@v3
@kokkisajee
// src/app/graphql.module.ts
import {NgModule} from '@angular/core';
import {ApolloModule, APOLLO_OPTIONS} from 'apollo-angular';
import {HttpLinkModule, HttpLink} from 'apollo-angular-link-http';
import {InMemoryCache} from 'apollo-cache-inmemory';
const uri = 'http://localhost:8080/graphql';
export function createApollo(httpLink: HttpLink) {
return {
link: httpLink.create({uri}),
cache: new InMemoryCache(),
};
}
@NgModule({
exports: [ApolloModule, HttpLinkModule],
providers: [
{
provide: APOLLO_OPTIONS,
useFactory: createApollo,
deps: [HttpLink],
},
],
})
export class GraphQLModule {}
# Install Apollo Angular
ng update apollo-angular@v4.1
@kokkisajee
<!-- src/app/app.component.html --> <h1> {{message}} </h1>
<!-- src/app/app.component.html --> <h1> {{message}} </h1>
<!-- src/app/app.component.html --> <h1> {{message}} </h1>
<!-- src/app/app.component.html --> <h1> {{message}} </h1>
@kokkisajee
// src/app/app.component.ts
import {Component} from '@angular/core';
import {Apollo} from 'apollo-angular';
import gql from 'graphql-tag';
const helloWorld = gql`
{
helloWorld
}
`;
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todoItems = ['Go for a walk with my dog', 'Buy some bread and butter', 'Learn how to paint'];
message: string = '';
constructor(private apollo: Apollo) {
this.apollo
.watchQuery<{helloWorld: string}>({query: helloWorld})
.valueChanges
.subscribe(result => {
this.message = result.data.helloWorld;
});
}
addTodo(title: string) {
this.todoItems = [title, ...this.todoItems];
}
}
type Query {
getTodos: [Todo!]!
}
type Todo {
id: ID!
title: String!
done: Boolean!
}
@kokkisajee
// graphql-todo-server/src/schema.ts
import 'graphql-import-node';
import * as typeDefs from './schema.graphql';
import {makeExecutableSchema} from 'graphql-tools';
import {GraphQLSchema} from 'graphql';
import {IResolvers} from 'graphql-tools';
export interface Todo {
id: string;
title: string;
done: boolean;
}
export const inMemoryDb: {todos: {[id: string]: Todo}} = {
todos: {
'1': {
id: '1',
title: 'Todo 1',
done: false
}
}
}
export const resolvers: IResolvers = {
Query: {
getTodos(): Todo[] {
return Object.values(inMemoryDb.todos);
}
}
};
export const schema: GraphQLSchema = makeExecutableSchema({
typeDefs,
resolvers
});
@kokkisajee
export interface Todo{
id: number;
title: string;
done: number;
}
@kokkisajee
<!-- src/app/app.component.html --> <mat-toolbar class="header"> <span role="header" color='primary'>GraphQL Todo</span> </mat-toolbar> <main class="container"> <div class="add-todo"> <mat-form-field class="add-todo-field"> <input #todoInput matInput (keyup.enter)="addTodo(todoInput.value); todoInput.value = ''" placeholder="Today, I learn about Angular, GraphQL and TypeScript!"> </mat-form-field> <button mat-mini-fab (click)="addTodo(todoInput.value); todoInput.value = ''"> <i class="material-icons">add</i> </button> </div> <mat-list> <mat-list-item *ngFor="let todo of todoItems" class="list"> <mat-checkbox color='primary'> {{ todo }} </mat-checkbox> </mat-list-item> </mat-list> </main>
<!-- src/app/app.component.html --> <mat-toolbar class="header"> <span role="header" color='primary'>GraphQL Todo</span> </mat-toolbar> <main class="container"> <div class="add-todo"> <mat-form-field class="add-todo-field"> <input #todoInput matInput (keyup.enter)="addTodo(todoInput.value); todoInput.value = ''" placeholder="Today, I learn about Angular, GraphQL and TypeScript!"> </mat-form-field> <button mat-mini-fab (click)="addTodo(todoInput.value); todoInput.value = ''"> <i class="material-icons">add</i> </button> </div> <mat-list> <mat-list-item *ngFor="let todo of todoItems" class="list"> <mat-checkbox color='primary'> {{ todo }} </mat-checkbox> </mat-list-item> </mat-list> </main>
<!-- src/app/app.component.html --> <mat-toolbar class="header"> <span role="header" color='primary'>GraphQL Todo</span> </mat-toolbar> <main class="container"> <div class="add-todo"> <mat-form-field class="add-todo-field"> <input #todoInput matInput (keyup.enter)="addTodo(todoInput.value); todoInput.value = ''" placeholder="Today, I learn about Angular, GraphQL and TypeScript!"> </mat-form-field> <button mat-mini-fab (click)="addTodo(todoInput.value); todoInput.value = ''"> <i class="material-icons">add</i> </button> </div> <mat-list> <mat-list-item *ngFor="let todo of todoItems" class="list"> <mat-checkbox color='primary'> {{ todo }} </mat-checkbox> </mat-list-item> </mat-list> </main>
<!-- src/app/app.component.html --> <mat-toolbar class="header"> <span role="header" color='primary'>GraphQL Todo</span> </mat-toolbar> <main class="container"> <div class="add-todo"> <mat-form-field class="add-todo-field"> <input #todoInput matInput (keyup.enter)="addTodo(todoInput.value); todoInput.value = ''" placeholder="Today, I learn about Angular, GraphQL and TypeScript!"> </mat-form-field> <button mat-mini-fab (click)="addTodo(todoInput.value); todoInput.value = ''"> <i class="material-icons">add</i> </button> </div> <mat-list> <mat-list-item *ngFor="let todo of todoItems" class="list"> <mat-checkbox color='primary'> {{ todo }} </mat-checkbox> </mat-list-item> </mat-list> </main>
@kokkisajee
// graphql-todo-app/src/app/app.component.ts
import { Component } from '@angular/core';
import {Apollo} from 'apollo-angular';
import gql from 'graphql-tag';
import {Todo} from '../../../graphql-todo-server/src/schema';
const allTodosQuery = gql`
query {
getTodos {
id
title
done
}
}
`;
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todoItems: Todo[] = [];
constructor(private apollo: Apollo) {
this.apollo
.watchQuery<{getTodos: Todo[]}>({
query: allTodosQuery
})
.valueChanges
.subscribe(result => {
this.todoItems = result.data.getTodos;
});
}
addTodo(title: string) {
// TODO: This will be implemented later
}
}
@kokkisajee
@kokkisajee
type Query {
getTodos: [Todo!]!
}
type Mutation {
addTodo(title: String!, done: Boolean! = false): [Todo!]!
updateTodo(id: ID!, done: Boolean!): [Todo!]!
}
type Todo {
id: ID!
title: String!
done: Boolean!
}
@kokkisajee
import 'graphql-import-node';
import * as typeDefs from './schema.graphql';
import {makeExecutableSchema} from 'graphql-tools';
import {GraphQLSchema} from 'graphql';
import {IResolvers} from 'graphql-tools';
import {v4 as uuidv4} from 'uuid';
export interface Todo {
id: string;
title: string;
done: boolean;
}
export const inMemoryDb: {todos: {[id: string]: Todo}} = {
todos: {
'1': {
id: '1',
title: 'Todo 1',
done: false
}
}
}
export const resolvers: IResolvers = {
Query: {
getTodos(): Todo[] {
return Object.values(inMemoryDb.todos);
}
},
Mutation: {
addTodo(_, {title, done = false}: Todo): Todo[] {
const id: string = uuidv4();
inMemoryDb.todos = {
...inMemoryDb.todos,
[id]: {
id,
title,
done
}
};
return Object.values(inMemoryDb.todos);
},
updateTodo(_, {id, done}: {id: string, done: boolean}): Todo[] {
inMemoryDb.todos = {
...inMemoryDb.todos,
[id]: {
...inMemoryDb.todos[id],
id,
done
}
};
return Object.values(inMemoryDb.todos);
}
}
};
export const schema: GraphQLSchema = makeExecutableSchema({
typeDefs,
resolvers
});
@kokkisajee
import { Component } from '@angular/core';
import {Apollo} from 'apollo-angular';
import gql from 'graphql-tag';
import {Todo} from '../../../graphql-todo-server/src/schema';
const allTodosQuery = gql`
query {
getTodos {
id
title
done
}
}
`;
const addTodoMutation = gql`
mutation addTodo($title: String!, $done: Boolean! = false) {
addTodo(title: $title, done: $done) {
id
title
done
}
}
`;
const updateTodoMutation = gql`
mutation updateTodo($id: ID!, $done: Boolean!) {
updateTodo(id: $id, done: $done) {
id
title
done
}
}
`;
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todoItems: Todo[] = [];
constructor(private apollo: Apollo) {
this.apollo
.watchQuery<{getTodos: Todo[]}>({
query: allTodosQuery
})
.valueChanges
.subscribe(result => {
this.todoItems = result.data.getTodos;
});
}
addTodo(title: string) {
this.apollo.mutate<{addTodo: Todo[]}>({
mutation: addTodoMutation,
variables: {
title
}
}).subscribe(result => {
if(result!=null && result.data!=null){
this.todoItems = result.data.addTodo;
}
})
}
updateTodo(id: string, done: boolean) {
this.apollo.mutate<{updateTodo: Todo[]}>({
mutation: updateTodoMutation,
variables: {
id,
done
}
}).subscribe(result => {
if(result!=null && result.data!=null){
this.todoItems = result.data.updateTodo;
}
})
}
}
@kokkisajee
<!-- graphql-todo-app/src/app/app.component.html -->
<mat-toolbar class="header">
<span role="header" color='primary'>GraphQL Todo</span>
</mat-toolbar>
<main class="container">
<div class="add-todo">
<mat-form-field class="add-todo-field">
<input #todoInput matInput
(keyup.enter)="addTodo(todoInput.value); todoInput.value = ''"
placeholder="Today, I learn about Angular, GraphQL and TypeScript!">
</mat-form-field>
<button mat-mini-fab (click)="addTodo(todoInput.value); todoInput.value = ''">
<i class="material-icons">add</i>
</button>
</div>
<mat-list #todoist class="todolist">
<mat-list-item *ngFor="let todo of todoItems" class="list">
<mat-checkbox color='primary'
[checked]="todo.done"
(change)="updateTodo(todo.id, !todo.done)">
{{ todo.title }}
</mat-checkbox>
</mat-list-item>
</mat-list>
</main>
@kokkisajee
Hope things go smoothly
@kokkisajee
Otherwise...
@kokkisajee
Introduction to JAMstack and why it's important
Graphql and it's core concepts
Get our hands dirty
@kokkisajee
Github Repository of the Workshop: sajeetharan/devfest2022: Devfest 2022 workshop sample (github.com)
Angular Website: https://angular.io/
Apollo Website: https://www.apollographql.com
GraphQL Website: https://graphql.org
JAMstack.org - More about JAMstack
@kokkisajee
@kokkisajee
I'm open to questions!
@kokkisajee
Thank you!
https://slides.com/sajeetharan/devfest2022workshopangular
@kokkisajee