Angular, Envoy Proxy and GraphQL
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
- Install Go in you local machine
(please refer to my blog post at https://medium.com/p/4b31bf248075 for detailed instructions.) - Clone baseline code from https://github.com/mayakoneval/bestiary
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
- Make sure that Docker Desktop is installed
- Make sure that Ubuntu on WSL is installed.
(please refer to my blog post at https://cloudjourney.medium.com/building-an-apollo-supergraph-locally-af0d80907260 for detailed instructions) - Ensure that you can successfully call the API from Ubuntu.
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 mycoolappgo 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”