Closed slimcdk closed 1 year ago
I'm not sure that I understand the issue and I don't see how it is related to the SDK.
Could you elaborate a little more on your setup? How are you deploying the Angular app? Is there a reverse proxy (eg. nginx) or you are using the pb_public
directory to serve static files that comes with PocketBase?
I suspect I somehow should be able to inject a or wrap the SDK in a HttpClient that Angular will use to make backend calls
I don't see why this will be needed. The SDK uses the native fetch
for sending requests and cannot be replaced (unless the Angular client has similar APIs).
I'm not very up-to-date with the latest Angular development flows and I'm not sure what to recommend, but you may want to check https://angular.io/guide/build#proxying-to-a-backend-server in case it is relevant to your use case.
I'm closing the issue because I don't think is related to the SDK but feel free to provide more details about your setup (or a minimal reproducible repo if possible) and I'll try to investigate it in more details.
I know this isn't necessarily an issue with this SDK, but I've been traversing through Angular documentation and Angular forums to find the solution and there seemingly isn't one. I'm not alone about this and I would imagine that existing PB user might have solved it so this is my last bullet in the chamber.
Angular intercepts all requests (for same domain) on the client and nothing is sent to the backend, unless you explicitly use the provided HttpClient. My eager hope was that you could inject your own http client. As long as it implements the needed interface. Very similar to the AuthStorage.
It might as well just be my setup that prevents Angular to forward request from the SDK to the backend instead of the routing mechanism. This is a typical (and minimal) routing configuration with wildcard routing to a 404 page.
const routes: Routes = [
{ path: '', component: HomePageComponent },
...
{ path: '**', component: PageNotFoundComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: []
})
The 'proxy to a backend server' unfortunate only works with the node runtime (primarly during development) and not if you do a production build with a static webserver like nginx, so this option is not available in production. Both PocketBase and Angular are running behind a reverse proxy (traefik) and Angular is being hosted from a nginx instance.
Both PocketBase and Angular are running behind a reverse proxy (traefik) and Angular is being hosted from a nginx instance.
My guess is that you are using nginx to rewrite all routes to the static index.html, right?
If that's the case, instead of passing down every route to the angular app, you can make an exception and configure the reverse proxy to proxy_pass all /api/*
routes to the backend server.
But without an actial traefik/nginx config sample how you are deploying your app, I'm not sure how to help.
If you need an example nginx config for PocketBase you can check https://pocketbase.io/docs/going-to-production/#using-reverse-proxy.
Additionally, please note for static client-side apps you don't really need nginx and you can put your files inside pb_public
directory next to the executable (you can change the default public dir location with the --publicDir
flag).
I'm only using nginx as a static webserver. The issue is however that Angular does not send anything to the backend, so I can't even rewrite the routes on my reverse proxy. Requests from the PocketBase SDK gets intercepted by the Angular router, because they aren't performed by the Angular HttpClient.
I'm aware of the pb_public
option but this sadly won't change how Angular and the SDK behaves on the client level. I can only imagine that if the SDK used Angulars HttpClient it would work, but I know that this isn't feasble for the SDK to adopt this. My workaround as of now is to simply serve PB's API on a subdomain, but I feel it kind of inflicts with the email linking
Here is my production config that I'm working on:
version: '3.8'
volumes:
traefik-ssl-certs:
driver: local
pb-data:
driver: local
driver_opts:
type: 'none'
o: 'bind'
device: '/mnt/volume_fra1_01'
services:
traefik:
image: traefik:v2.9
environment:
- DO_AUTH_TOKEN=${DO_TRAEFIK_CERT}
healthcheck:
test: traefik healthcheck --ping
interval: 10s
timeout: 5s
retries: 3
start_period: 5s
volumes:
- traefik-ssl-certs:/ssl-certs
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- 80:80
- 443:443
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entrypoints.ping.address=:8080
- --ping.entrypoint=ping
- --certificatesresolvers.production.acme.dnschallenge=true
- --certificatesresolvers.production.acme.dnschallenge.provider=digitalocean
- --certificatesresolvers.production.acme.email=${EMAIL}
- --certificatesresolvers.production.acme.storage=/ssl-certs/acme.json
- --certificatesresolvers.production.acme.caserver=https://acme-v02.api.letsencrypt.org/directory
- --certificatesresolvers.production2.acme.dnschallenge=true
- --certificatesresolvers.production2.acme.dnschallenge.provider=digitalocean
- --certificatesresolvers.production2.acme.email=${EMAIL}
- --certificatesresolvers.production2.acme.storage=/ssl-certs/acme2.json
- --certificatesresolvers.production2.acme.caserver=https://acme-v02.api.letsencrypt.org/directory
restart: unless-stopped
backend:
image: ghcr.io/slimcdk/<my-angular-nginx>
volumes:
- pb-data:/data
labels:
- traefik.enable=true
- traefik.http.routers.backend.entrypoints=websecure
- traefik.http.routers.backend.tls=true
- traefik.http.routers.backend.tls.certresolver=production
- traefik.http.routers.backend.rule=Host(`backend.${DOMAIN}`)
# - traefik.http.routers.backend.rule=(Host(`${DOMAIN}`) && Path(`/api`))
- traefik.http.services.backend.loadbalancer.server.port=8090
command: serve --http=0.0.0.0:8090 --dir=/data --debug=true
restart: unless-stopped
frontend:
image: ghcr.io/slimcdk/<my-pocketbase-as-a-framework>
labels:
- traefik.enable=true
- traefik.http.routers.frontend.entrypoints=websecure
- traefik.http.routers.frontend.tls=true
- traefik.http.routers.frontend.tls.certresolver=production
- traefik.http.routers.frontend.rule=Host(`${DOMAIN}`)
- traefik.http.services.frontend.loadbalancer.server.port=80
restart: unless-stopped
gotenberg:
image: thecodingmachine/gotenberg:6
labels:
- traefik.enable=false
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
deploy:
replicas: 2
restart: unless-stopped
Dockerfile for the my-angular-nginx image.
FROM node:18 AS deps
WORKDIR /workspace
COPY . .
RUN npm install -g npm@latest @angular/cli@^15
RUN npm install
FROM deps AS build
WORKDIR /workspace
ARG API_DOMAIN=""
ENV NG_APP_API_DOMAIN=$API_DOMAIN
RUN ng build --configuration=production
FROM nginx:stable-alpine
COPY --from=build /workspace/dist/frontend /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
HEALTHCHECK --start-period=5s --interval=10s --timeout=2s --retries=3 CMD curl --fail http://localhost:80/health || exit 1
CMD ["nginx", "-g", "daemon off;"]
Dockerfile for my-pocketbase-as-a-framework image.
# Cache dependencies
FROM golang:1.20 as build-deps
WORKDIR /src
ADD go.mod .
ADD go.sum .
RUN go mod download -x
# Build binary
FROM build-deps as build-env
COPY . .
RUN CGO_ENABLED=0 go build -ldflags "-s -w" -v -o /builds/main /src/server/*
FROM alpine:3.17 as alpine
# Generate TLS certificates
RUN apk add -U --no-cache ca-certificates curl
COPY --from=build-env /builds /usr/local/bin
VOLUME /data
EXPOSE 8090
HEALTHCHECK --start-period=5s --interval=10s --timeout=2s --retries=3 CMD curl --fail http://localhost:8090/api/health || exit 1
ENTRYPOINT [ "/usr/local/bin/main" ]
CMD ["serve", "--http=0.0.0.0:8090", "--dir=/data", "--debug=false"]
nginx config
server {
listen 80;
listen [::]:80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ /index.html;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location = /health {
access_log off;
add_header 'Content-Type' 'application/json';
return 200 '{"status":"UP"}';
}
}
Apologies for the many edits.
Requests from the PocketBase SDK gets intercepted by the Angular router, because they aren't performed by the Angular HttpClient.
But how? I can't find any reference in the Angular documentation for modified/overwritten fetch
.
Your setup is too complicated and I'm not sure how to properly reproduce it.
I've tried:
npm run build
to generate the static filespb_public
directoryI found the culprit. It was the developer all along.. I had configured my reverse proxy to redirect on exact /api
match and not as a prefix... Everything now works smoothly. Thanks again for this awesome project. And for your time trying to reproduce my issue even though it wasn't remotely related to this SDK.
Hello folks
I'm really impressed with the quality of this project. I plan on serving Angular and PocketBase on same domain, where PocketBase's API will be accessible on
/api
however Angular's routing mechanism seem to intercept any calls to this path as navigation and not as calls to my backend server.I'm initializing PocketBase in an Angular service that I inject throughout my application. So far this works fine as long as PocketBase is served on another domain/port. I suspect I somehow should be able to inject a or wrap the SDK in a
HttpClient
that Angular will use to make backend calls https://angular.io/guide/http. But how could this be done?