Coimbatore Front End
User Group (June 2022)
Coimbatore Front End
User Group (June2022)
In a nutshell
Communities
Social Developer
Recognitions
Senior Product Manager Cosmosdb
GDE,MCT and former MVP
Top stackoverflow contributor
@sajeetharan
@kokkisajee
@sajeetharan
@sajeetharan
👨🎨 Front-End developers ?
👨💻 Back-End developers ?
🦸♂️ Something else ? something in between ?
☁️ Cloud developers ?
👍A “Beginner session"
👍It won’t teach you everything, But it will help you learn what you need to know to get started
👍Be ready to Unmute and ask questions
Introduction to JAMstack and why it's important
Graphql and it's core concepts
Get our hands dirty
Kahoot Quiz and QnA
How to Build JAM stack App with Graphql
Server
1. Request
2. HTML
Backend
1. Request
4. Dynamically generated HTML
DB
2. Query
3. Results
But: "Click and wait" for every page
Server
1. Request
2. HTML
3. AJAX Request
4. XML (oder JSON)
Linux
Apache
MySQL
PHP
Mongo
Express
Angular
Node
Mongo
Express
React
Node
Traditional Work Flow
WebServer
Client
AppServer
DB
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
Traditional SSR
"Old" Static pages
Single Page Apps
Server Side Rendering
JAMstack
CDN
Client
DB
avascript
PIs
arkup
Static, won't be modified by server
Generated on build-time (before (re-)deploying)
Usually compiled/built via Static Site Generator (SSG)
<app-make-meme *ngIf="imgUrl" [imageUrl]="imgUrl"></app-make-meme>
Responsible for the website's dynamics
Only on client-side
Independent of frameworks or libraries
Used for interacting with the website (comments, forms...)
Called through Javascript and HTTPS
Good fit: Microservices, Serverless Functions or similar
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 (_) { /* */}
}
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!
Performance
Simplicity
Security
Cost
Only static HTML files, no .php / .py / ...
You don't need a server*
No difficult deploy pipelines anymore!
*hosting via. GitHub pages oder Netlify
No content generated "on the fly". Static files!
API endpoints are small and ideally isolated
Good practice: "Content API" only available at build time
Improved Time to first Byte due to CDN
Easy Cache Invalidation
Quick Response Time as it is just HTML
Ideal for SEO
No server, no server fees!
Free Hosting (via Netlify or GH Pages)
Serverless APIs are billed by usage
Sajee
Sachin
Dhoni
"CRUD pls"
Sajee
Sachin
"small change pls"
release
"small change pls"
"small change pls"
"small change pls"
http://facebook.github.io/graphql/draft/
Sajee
Sachin
Dhoni
"CRUD pls"
Sajee
Sachin
"small change pls"
release
"small change pls"
"small change pls"
"small change pls"
Sajee
My3
MR
GraphQL
QUERY
MUTATION
SUBSCRIPTION
Sanga Mahela Quit Cricket
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": "Madhan",
"alias":madhang
"Comments": {
"name": ""
},
"Sessions": [
{ "title": "Database Internals" },
{ "title": "Graphql" }
}
}
}
{
"me": {
"name": "Dinesh Kuamr"
}
}
{
me {
name
}
}
Basic concepts
Query
Mutation
Subscription
Basic concepts
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
Query
Mutation
Subscription
Basic concepts
mutation CREATE_POST($value: String!) {
createPost(value: $value) {
}
}
author {
name
avatar_url(size: 40)
}
body
id
date
...
Query
Mutation
Subscription
Basic concepts
subscription FEED_UPDATES {
postAdded {
}
}
author {
name
avatar_url(size: 40)
}
body
id
date
...
query getUser($withFriends: Boolean) {
me {
name
friends @include(if: $withFriends) {
name
}
}
}
Basic concepts : Directive
Basic concepts
schema: {
query: Query
mutation: Mutation
subscription: Subscription
}
Query: {
user(obj, args, context, info) {
return context.db.loadUserByID(args.id)
}
}
User: {
name(obj, args, context, info) {
return obj.name
}
}
An in-browser IDE for exploring GraphQL.
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 |
Download
https://nodejs.org/en/download/
https://code.visualstudio.com/
Install
npm install -g @angular/cli@latest
# Create project folder
mkdir graphql-todo && cd graphql-todo
# Create sub-folder for our server
mkdir graphql-todo-server && cd graphql-todo-server
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
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.
# 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
// 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" }, // ...
// 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`)
);
// src/schema.graphql
type Query {
helloWorld: String!
}
// 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
});
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
# Create todo app (in the graphql-todo folder!)
ng new graphql-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 graphql-todo-app
#start the application
npm start
# Using the CLI we can add ng addable libraries
ng add @angular/material
# ? 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
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 { }
<!-- 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.title }} </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.title }} </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.title }} </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.title }} </mat-checkbox> </mat-list-item> </mat-list> </main>
// src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todoItems = ['Go for a walk and gym', 'Buy some bread and butter', 'Join Graphql session'];
addTodo(title: string) {
this.todoItems = [title, ...this.todoItems];
}
}
# Install Apollo Angular
ng add apollo-angular
// 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 {}
// 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!
}
// 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
});
// 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
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
Kahoot Quiz and QnA
@kokkisajee
Github Repository of the Workshop: sajeetharan/cmbt-frontend-graphql (github.com)
Angular Website: https://angular.io/
Apollo Website: https://www.apollographql.com
GraphQL Website: https://graphql.org
JAMstack.org - More about JAMstack
@kokkisajee
I'm open to questions!
@kokkisajee
Thank you!
https://slides.com/sajeetharan/jamstack/