Angular, Envoy Proxy and GraphQL

Cloud Journey
14 min readJun 26, 2023

--

Overview

In this blog post, I will demonstrate how to expose a Golang GraphQL API through an Envoy proxy and how to execute GraphQL queries from an Angular Apollo client.

Coding Structure

Here is a screenshot of the coding structure: the ‘api’ directory contains Go GraphQL code, the ‘client’ directory contains Angular apollo code, and the ‘envoy’ directory containss the Envoy proxy code.

Golang GraphQL API

Prerequisites

Cross Origin Resource Sharing (CORS)

The API runs at https://localhost:443, and the UI runs at https://localhost:8443. In order to call the API from the UI, we need to configure the API to allow CORS.

func configCors(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "https://localhost:8443")
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Content-Length, Accept-Encoding")

// I added this for another handler of mine,
// but I do not think this is necessary for GraphQL's handler
if r.Method == "OPTIONS" {
w.Header().Set("Access-Control-Max-Age", "86400")
w.WriteHeader(http.StatusOK)
return
}

h.ServeHTTP(w, r)
})
}

HTTPS

To create a self-signed certificate and update the code to listen on port 443, follow these steps.

cd api
#create certificate
openssl req -x509 -newkey rsa:4096 -keyout apicert.key -out apicert.pem -days 365 -nodes -config cert-input.txt
# check if two DNS names are created in the certificate
openssl x509 -in apicert.pem -text

I configured two DNS names in cert-input.txt.

[req]
default_bits = 4096
prompt = no
default_md = sha256
x509_extensions = req_ext
req_extensions = req_ext
distinguished_name = dn
[ dn ]
C=US
ST=New York
L=New York
O=MyOrg
OU=MyOrgUnit
CN = localhost
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = localhost
DNS.2 = DESKTOP-TMK1GG5.local

Here is the updated main.go file.

package main

import (
"net/http"

"github.com/graphql-go/handler"
)

func configCors(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "https://localhost:8443")
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Content-Length, Accept-Encoding")

// I added this for another handler of mine,
// but I do not think this is necessary for GraphQL's handler
if r.Method == "OPTIONS" {
w.Header().Set("Access-Control-Max-Age", "86400")
w.WriteHeader(http.StatusOK)
return
}

h.ServeHTTP(w, r)
})
}

func main() {
h := handler.New(&handler.Config{
Schema: &BeastSchema,
Pretty: true,
GraphiQL: false,
})

http.Handle("/graphql", configCors(h))

http.Handle("/sandbox", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(sandboxHTML)
}))

http.ListenAndServeTLS(":443", "apicert.pem", "apicert.key", nil)

}

var sandboxHTML = []byte(`
<!DOCTYPE html>
<html lang="en">
<body style="margin: 0; overflow-x: hidden; overflow-y: hidden">
<div id="sandbox" style="height:100vh; width:100vw;"></div>
<script src="https://embeddable-sandbox.cdn.apollographql.com/_latest/embeddable-sandbox.umd.production.min.js"></script>
<script>
new window.EmbeddedSandbox({
target: "#sandbox",
// Pass through your server href if you are embedding on an endpoint.
// Otherwise, you can pass whatever endpoint you want Sandbox to start up with here.
initialEndpoint: "https://localhost:443/graphql",
});
// advanced options: https://www.apollographql.com/docs/studio/explorer/sandbox#embedding-sandbox
</script>
</body>

</html>`)

Validation

Execute a GraphQL query at https://localhost:443/graphql using an HTTPS request.

cd api
go run .
curl --cacert apicert.pem -X POST -H "Content-Type: application/json" --data '{ "query": "{ beastList {id name } }" }' https://localhost:443/graphql

"data": {
"beastList": [
{
"id": 1,
"name": "Amphisbaena"
},
{
"id": 2,
"name": "Monocerus"
},
{
"id": 3,
"name": "Manticore"
},
......
{
"id": 10,
"name": "Yale"
}
]
}
}

Envoy Proxy

Since we will be installing and running Envoy proxy using Docker, please ensure that the prerequisites are set up beforehand.

Prerequisites

Execute a GraphQL query at https://<ubuntu wsl-hostname>.local:443/graphql using an HTTPS request.

nino@DESKTOP-TMK1GG5:~$ curl --cacert apicert.pem -X POST -H "Content-Type: application/json" --data '{ "query": "{ beastList {id name } }" }' https://DESKTOP-TMK1GG5.local:443/graphql

"data": {
"beastList": [
{
"id": 1,
"name": "Amphisbaena"
},
{
"id": 2,
"name": "Monocerus"
},
{
"id": 3,
"name": "Manticore"
},
...

Install Envoy Using Docker

Start Ubuntu and pull docker image.

Please refer to official document at https://www.envoyproxy.io/docs/envoy/latest/start/install#install-envoy-using-docker for detailed instructions.

docker pull envoyproxy/envoy:dev-b64e1f6d1e2f1d868d77e0d0f6f517ea0101c312
docker run --rm envoyproxy/envoy:dev-b64e1f6d1e2f1d868d77e0d0f6f517ea0101c312 --version

Custom Envoy Configuration

Add ‘envoy-custom.yaml’ file.

nino@DESKTOP-TMK1GG5:~$ ls -ltr
total 4
-rwxr-xr-x 1 nino nino 1699 Jun 19 22:31 envoy-custom.yaml

Here is the envoy-custom.yaml file. The hostname of my Ubuntu on WSL is ‘DESKTOP-TMK1GG5’.

admin:
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 9901
static_resources:
listeners:
- name: listener_0
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 9443
filter_chains:
-
# Configures a transport socket protocol like TLS or ALTS.
# To specify a specific type, a "typed_config" field with valid "@type" name is required.
# https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#envoy-v3-api-msg-config-core-v3-transportsocket
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
# A transport socket listening to downstream connections (clients) using TLS.
# https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/transport_sockets/tls/v3/tls.proto#envoy-v3-api-msg-extensions-transport-sockets-tls-v3-downstreamtlscontext
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
# TLS contextual information shared by both the client and server.
# https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/transport_sockets/tls/v3/tls.proto#envoy-v3-api-msg-extensions-transport-sockets-tls-v3-commontlscontext
common_tls_context:
# A list of TLS certificates that can be used, clients will only choose a single one per session.
# https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/transport_sockets/tls/v3/common.proto#envoy-v3-api-msg-extensions-transport-sockets-tls-v3-tlscertificate
tls_certificates:
-
# A datasource from which to read the public key certificate, such as a file or
# environment variable.
# https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#envoy-v3-api-msg-config-core-v3-datasource
certificate_chain:
filename: envoycert.pem
# A datasource from which to read the private key, such as a file or environment variable.
# https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#envoy-v3-api-msg-conf
private_key:
filename: envoycert.key
# An ordered list of filters to apply to connections.
# https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/listener/v3/listener_components.proto#envoy-v3-api-msg-config-listener-v3-filter

filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/graphql"
route:
cluster: service_api_graphql
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: service_api_graphql
type: LOGICAL_DNS
# Comment out the following line to test on v6 networks
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: service_api_graphql
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: DESKTOP-TMK1GG5.local
port_value: 443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
validation_context:
trusted_ca:
filename: apicert.pem
sni: DESKTOP-TMK1GG5.local

Create Certificate

Since the Envoy runs on localhost as well, we will utilize the same certificate as the API service

$ cd misc/envoy 
$ cp misc/api/apicert.key envoycert.key
$ cp misc/api/apicert.key .
$ cp misc/api/apicert.pem envoycert.pem

Run Envoy with Custom Configuration

Please refer to official document at https://www.envoyproxy.io/docs/envoy/latest/start/quick-start/run-envoy#run-envoy-with-the-demo-configuration for detailed instructions.

nino@DESKTOP-TMK1GG5:~$ ping -c 1 DESKTOP-TMK1GG5.local
PING DESKTOP-TMK1GG5 (172.21.176.1) 56(84) bytes of data.
64 bytes from DESKTOP-TMK1GG5.mshome.net (172.21.176.1): icmp_seq=1 ttl=128 time=0.181 ms

docker run --rm -it \
-v $(pwd)/envoy-custom.yaml:/envoy-custom.yaml \
-v $(pwd)/apicert.pem:/apicert.pem \
-v $(pwd)/envoycert.pem:/envoycert.pem \
-v $(pwd)/envoycert.key:/envoycert.key \
-p 9901:9901 \
-p 9443:9443 \
--add-host DESKTOP-TMK1GG5.local:172.21.176.1 \
envoyproxy/envoy:dev-b64e1f6d1e2f1d868d77e0d0f6f517ea0101c312 \
-c envoy-custom.yaml

Envoy Proxy Validation

Execute a GraphQL query at Envoy proxy endpoint https://localhost:9443/graphql using an HTTPS request.

$ curl --cacert envoycert.pem  -X POST -H "Content-Type: application/json" --data '{ "query": "{ beastList {id name } }" }' https://localhost:9443/graphql
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 558 100 519 100 39 34073 2560 --:--:-- --:--:-- --:--:-- 37200{
"data": {
"beastList": [
{
"id": 1,
"name": "Amphisbaena"
},
{
"id": 2,
"name": "Monocerus"
},
{
......

Angular Apollo

Establish Baseline Code

# install Angular CLI
npm install -g @angular/cli
# create a Angular project in current directory
cd misc/client
ng new angular-graphql --directory ./
# install GraphQL client for Angular
ng add apollo-angular

Please ensure that in client\src\app\graphql.module.ts, you configure the variable uri to 'https://localhost:9443/graphql'.

Queries

Please add the ‘graphql’ folder under the app and ensure that you have the file namedclient\src\app\graphql\graphql.queries.ts.

import {gql} from 'apollo-angular'

const GET_BEASTS = gql`
query {
beastList {
id
name
description
}
}
`
export {GET_BEASTS}

Component

#create a new component
ng generate component beast-list --module app

Your beast-list.component.ts and beast-list.component.html should look like the following code snippet.

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Apollo } from 'apollo-angular';
import { GET_BEASTS } from '../graphql/graphql.queries';

@Component({
selector: 'app-beast-list',
templateUrl: './beast-list.component.html',
styleUrls: ['./beast-list.component.css']
})
export class BeastListComponent implements OnInit {
BeastList: any[] = [];
error: any;

beastForm = new FormGroup({
name: new FormControl('', Validators.required),
description: new FormControl('', Validators.required)
});

constructor(private apollo: Apollo) { }

ngOnInit(): void {
this.apollo.watchQuery({
query: GET_BEASTS
}).valueChanges.subscribe(({ data, error }: any) => {
this.BeastList = data.beastList;
this.error = error;
}
);
}
}
<div class="main">
<h3>Beast List</h3>
<div *ngIf="error">
<p>Error: {{ error }}</p>
</div>


<div class="beast-container" *ngIf="BeastList">
<ul>
<li *ngFor="let beast of BeastList">
<div class="beast">
<span class="beast-name">{{ beast.name }}</span>
<span class="beast-description">{{ beast.description }}</span>
</div>
</li>
</ul>
</div>
</div>

Sample app.component.html

<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * Delete the template below * * * * * * * * * * -->
<!-- * * * * * * * to get started with your project! * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->

<style>
:host {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 14px;
color: #333;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
h4,
h5,
h6 {
margin: 8px 0;
}

p {
margin: 0;
}

.spacer {
flex: 1;
}

.toolbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 60px;
display: flex;
align-items: center;
background-color: #1976d2;
color: white;
font-weight: 600;
}

.toolbar img {
margin: 0 16px;
}

.toolbar #twitter-logo {
height: 40px;
margin: 0 8px;
}

.toolbar #youtube-logo {
height: 40px;
margin: 0 16px;
}

.toolbar #twitter-logo:hover,
.toolbar #youtube-logo:hover {
opacity: 0.8;
}

.content {
display: flex;
margin: 82px auto 32px;
padding: 0 16px;
max-width: 960px;
flex-direction: column;
align-items: center;
}

svg.material-icons {
height: 24px;
width: auto;
}

svg.material-icons:not(:last-child) {
margin-right: 8px;
}

.card svg.material-icons path {
fill: #888;
}

.card-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-top: 16px;
}

.card {
all: unset;
border-radius: 4px;
border: 1px solid #eee;
background-color: #fafafa;
height: 40px;
width: 200px;
margin: 0 8px 16px;
padding: 16px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
transition: all 0.2s ease-in-out;
line-height: 24px;
}

.card-container .card:not(:last-child) {
margin-right: 0;
}

.card.card-small {
height: 16px;
width: 168px;
}

.card-container .card:not(.highlight-card) {
cursor: pointer;
}

.card-container .card:not(.highlight-card):hover {
transform: translateY(-3px);
box-shadow: 0 4px 17px rgba(0, 0, 0, 0.35);
}

.card-container .card:not(.highlight-card):hover .material-icons path {
fill: rgb(105, 103, 103);
}

.card.highlight-card {
background-color: #1976d2;
color: white;
font-weight: 600;
border: none;
width: auto;
min-width: 30%;
position: relative;
}

.card.card.highlight-card span {
margin-left: 60px;
}

svg#rocket {
width: 80px;
position: absolute;
left: -10px;
top: -24px;
}

svg#rocket-smoke {
height: calc(100vh - 95px);
position: absolute;
top: 10px;
right: 180px;
z-index: -10;
}

a,
a:visited,
a:hover {
color: #1976d2;
text-decoration: none;
}

a:hover {
color: #125699;
}

.terminal {
position: relative;
width: 80%;
max-width: 600px;
border-radius: 6px;
padding-top: 45px;
margin-top: 8px;
overflow: hidden;
background-color: rgb(15, 15, 16);
}

.terminal::before {
content: "\2022 \2022 \2022";
position: absolute;
top: 0;
left: 0;
height: 4px;
background: rgb(58, 58, 58);
color: #c2c3c4;
width: 100%;
font-size: 2rem;
line-height: 0;
padding: 14px 0;
text-indent: 4px;
}

.terminal pre {
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
color: white;
padding: 0 1rem 1rem;
margin: 0;
}

.circle-link {
height: 40px;
width: 40px;
border-radius: 40px;
margin: 8px;
background-color: white;
border: 1px solid #eeeeee;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
transition: 1s ease-out;
}

.circle-link:hover {
transform: translateY(-0.25rem);
box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.2);
}

footer {
margin-top: 8px;
display: flex;
align-items: center;
line-height: 20px;
}

footer a {
display: flex;
align-items: center;
}

.github-star-badge {
color: #24292e;
display: flex;
align-items: center;
font-size: 12px;
padding: 3px 10px;
border: 1px solid rgba(27,31,35,.2);
border-radius: 3px;
background-image: linear-gradient(-180deg,#fafbfc,#eff3f6 90%);
margin-left: 4px;
font-weight: 600;
}

.github-star-badge:hover {
background-image: linear-gradient(-180deg,#f0f3f6,#e6ebf1 90%);
border-color: rgba(27,31,35,.35);
background-position: -.5em;
}

.github-star-badge .material-icons {
height: 16px;
width: 16px;
margin-right: 4px;
}

svg#clouds {
position: fixed;
bottom: -160px;
left: -230px;
z-index: -10;
width: 1920px;
}

/* Responsive Styles */
@media screen and (max-width: 767px) {
.card-container > *:not(.circle-link) ,
.terminal {
width: 100%;
}

.card:not(.highlight-card) {
height: 16px;
margin: 8px 0;
}

.card.highlight-card span {
margin-left: 72px;
}

svg#rocket-smoke {
right: 120px;
transform: rotate(-5deg);
}
}

@media screen and (max-width: 575px) {
svg#rocket-smoke {
display: none;
visibility: hidden;
}
}
</style>

<!-- Toolbar -->
<div class="toolbar" role="banner">
<img
width="40"
alt="Angular Logo"
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
/>
<span>Welcome</span>
<div class="spacer"></div>
<a aria-label="Angular on twitter" target="_blank" rel="noopener" href="https://twitter.com/angular" title="Twitter">
<svg id="twitter-logo" height="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
<rect width="400" height="400" fill="none"/>
<path d="M153.62,301.59c94.34,0,145.94-78.16,145.94-145.94,0-2.22,0-4.43-.15-6.63A104.36,104.36,0,0,0,325,122.47a102.38,102.38,0,0,1-29.46,8.07,51.47,51.47,0,0,0,22.55-28.37,102.79,102.79,0,0,1-32.57,12.45,51.34,51.34,0,0,0-87.41,46.78A145.62,145.62,0,0,1,92.4,107.81a51.33,51.33,0,0,0,15.88,68.47A50.91,50.91,0,0,1,85,169.86c0,.21,0,.43,0,.65a51.31,51.31,0,0,0,41.15,50.28,51.21,51.21,0,0,1-23.16.88,51.35,51.35,0,0,0,47.92,35.62,102.92,102.92,0,0,1-63.7,22A104.41,104.41,0,0,1,75,278.55a145.21,145.21,0,0,0,78.62,23" fill="#fff"/>
</svg>
</a>
<a aria-label="Angular on YouTube" target="_blank" rel="noopener" href="https://youtube.com/angular" title="YouTube">
<svg id="youtube-logo" height="24" width="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fff">
<path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M21.58 7.19c-.23-.86-.91-1.54-1.77-1.77C18.25 5 12 5 12 5s-6.25 0-7.81.42c-.86.23-1.54.91-1.77 1.77C2 8.75 2 12 2 12s0 3.25.42 4.81c.23.86.91 1.54 1.77 1.77C5.75 19 12 19 12 19s6.25 0 7.81-.42c.86-.23 1.54-.91 1.77-1.77C22 15.25 22 12 22 12s0-3.25-.42-4.81zM10 15V9l5.2 3-5.2 3z"/>
</svg>
</a>
</div>

<div class="content" role="main">

<app-beast-list></app-beast-list>


<svg id="clouds" xmlns="http://www.w3.org/2000/svg" width="2611.084" height="485.677" viewBox="0 0 2611.084 485.677">
<title>Gray Clouds Background</title>
<path id="Path_39" data-name="Path 39" d="M2379.709,863.793c10-93-77-171-168-149-52-114-225-105-264,15-75,3-140,59-152,133-30,2.83-66.725,9.829-93.5,26.25-26.771-16.421-63.5-23.42-93.5-26.25-12-74-77-130-152-133-39-120-212-129-264-15-54.084-13.075-106.753,9.173-138.488,48.9-31.734-39.726-84.4-61.974-138.487-48.9-52-114-225-105-264,15a162.027,162.027,0,0,0-103.147,43.044c-30.633-45.365-87.1-72.091-145.206-58.044-52-114-225-105-264,15-75,3-140,59-152,133-53,5-127,23-130,83-2,42,35,72,70,86,49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33,61.112,8.015,113.854-5.72,150.492-29.764a165.62,165.62,0,0,0,110.861-3.236c47,94,178,113,251,33,31.385,4.116,60.563,2.495,86.487-3.311,25.924,5.806,55.1,7.427,86.488,3.311,73,80,204,61,251-33a165.625,165.625,0,0,0,120,0c51,13,108,15,157-5a147.188,147.188,0,0,0,33.5-18.694,147.217,147.217,0,0,0,33.5,18.694c49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33C2446.709,1093.793,2554.709,922.793,2379.709,863.793Z" transform="translate(142.69 -634.312)" fill="#eee"/>
</svg>

</div>

<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->

<router-outlet></router-outlet>

Enable HTTPS

First copy over the private key and certificate files.

$ cd misc/client 
$ cp misc/api/apicert.key client.key
$ cp misc/api/apicert.pem client.pem

Open Chrome security settings, then import the self-signed certificate into the ‘trusted root certifications authorities’. You can import either apicert.pem, envoycert.pem, or client.pem. All of them are the same certificate.

After importing, you can open https://localhost:443, https://localhost:9443, and https://localhost:8443 in the Chrome browser. If you do not encounter any security warnings or certificate errors, it indicates that the certificate import was successful.

Validation

Start Angular project:

cd client
ng serve --port 8443 --ssl -ssl-key client.key --ssl-cert client.pem

Open https://localhost:8443 in the Chrome browser. You should be able to see the response from the GraphQL query.

Conclusion

Congratulations! We have successfully made a GraphQL API call through the Envoy proxy from the UI.

GraphQL queries from Angular Apollo client can be routed through an envoy proxy HTTP filter. Graphql-go is a popular GraphQL server library written in go. We also utilize graphql-go/handler to enable the execution of GraphQL queries over HTTP requests.

In the next blog post, I will use Envoy Proxy as a front proxy for both the Angular app and the GraphQL API.

References

“Install go All releases — The Go Programming Language
“Enable HTTPS/TLS for golang API Simple Golang HTTPS/TLS Examples (github.com)
https://github.com/denji/golang-tls
“Cheat-sheet, create a go project
mkdir mycoolapp
cd mycoolapp
go mod init itec-stash.fmr.com/ap123456/mycoolapp"
“Print headers in go A simple HTTP server in Golang which just prints all the headers (github.com)

Building a GraphQL server in Go with go-graphql — Apollo GraphQL Blog
https://github.com/mayakoneval/bestiary

“angular https://signoz.io/blog/angular-graphql/
“golang api cors
https://github.com/graphql-go/graphql/issues/290
https://github.com/graph-gophers/graphql-go/issues/74
“Angular https://www.makeuseof.com/routing-angular-basic/
InDepth guide to passing data via routing — Angular Tutorials | indepth.dev
Using Services to Share Data and Functionality Across Components in Angular | by Nic Chong | Level Up Coding (gitconnected.com)

“copy file from Windows to wsl: https://megamorf.gitlab.io/2021/08/11/copy-files-from-windows-to-your-wsl-instance/

# Windows drive can be accessed from WSL using /mnt/<drive>
nino@DESKTOP-TMK1GG5:~$ tar -zxvf /mnt/d/helm-v3.12.1-linux-amd64.tar.gz

WSL Ubuntu reset password:“https://learn.microsoft.com/en-us/windows/wsl/setup/environment
“install docker desktop in D drive https://forums.docker.com/t/docker-installation-directory/32773/17 start /w “” “Docker Desktop Installer.exe” install — installation-dir=G:\Docker” “https://github.com/docker/roadmap/issues/94
"Docker Desktop Installer.exe" install --installation-dir="D:\Program Files\Docker"

“from wsl, access api running locally in windows, needs to use Ubuntu wsl ip address https://stackoverflow.com/questions/64763147/access-a-localhost-running-in-windows-from-inside-wsl2

“protect a component, show only after login routes — Angular 13 routing to Login page — Stack Overflow

“envoy receives https request https://www.funnel-labs.io/2022/10/19/envoyproxy-5-securing-connections-with-https/#configuring-envoy-to-receive-https-connections
“envoy trusted ca https://github.com/envoyproxy/envoy/issues/6763

--

--

Cloud Journey
Cloud Journey

Written by Cloud Journey

All blogs are strictly personal and do not reflect the views of my employer. https://github.com/Ronnie-personal