Skip to content

Commit

Permalink
feat: proxy frontend API requests to correct microservice
Browse files Browse the repository at this point in the history
* Add feeds component

* refactor frontend to use proxy instead of the services directly

* add api service class to frontend
  • Loading branch information
fabioDMFerreira committed Sep 21, 2023
1 parent 583d5e3 commit c9fe33f
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 63 deletions.
3 changes: 3 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,6 @@ services:
- '/app/node_modules'
ports:
- 3000:3000
depends_on:
- api
- news
1 change: 1 addition & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@types/node": "^16.18.46",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"http-proxy-middleware": "^2.0.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
Expand Down
43 changes: 22 additions & 21 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import './App.css';
import WebSocketComponent from './components/WebsocketComponent';
import UserInfo from './components/UserInfo';
import api from './services/api';
import Feeds from './components/Feeds';

function App() {
const [name, setName] = useState<string>();
Expand All @@ -10,29 +12,27 @@ function App() {
const [isLoggedIn, setIsLoggedIn] = useState<boolean>();
const [loginError, setLoginError] = useState<string>();

useEffect(() => {
if (api.token) {
setIsLoggedIn(true);
}
}, []);

const login = useCallback(() => {
if (!name || !password) {
return;
}

setLoginError('');

fetch('http://localhost:8000/auth/login', {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, password }),
})
.then(async (resp) => {
if (resp.status === 200) {
const json = await resp.json();
setIsLoggedIn(true);
setToken(json.token);
} else {
const err = await resp.text();
setLoginError(err);
}
api
.login(name, password)
.then((resp) => {
setIsLoggedIn(true);
setToken(resp.token);
})
.catch((err) => {
console.log(err);
setLoginError(err);
});
}, [name, password]);

Expand Down Expand Up @@ -66,8 +66,9 @@ function App() {
)}
{isLoggedIn && token && (
<div>
<UserInfo token={token} />
<WebSocketComponent token={token} />
<UserInfo />
<Feeds />
<WebSocketComponent />
</div>
)}
</div>
Expand Down
26 changes: 26 additions & 0 deletions frontend/src/components/Feeds.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { useEffect, useState } from 'react';
import api, { Feeds } from '../services/api';

const FeedsComponent = () => {
const [feeds, setFeeds] = useState<Feeds>();

useEffect(() => {
api.feeds().then(setFeeds).catch(console.log);
}, []);

return (
<>
{feeds
? feeds.map((feed) => {
return (
<div>
{feed.title} <button>Subscribe</button>
</div>
);
})
: 'No feeds'}
</>
);
};

export default FeedsComponent;
17 changes: 6 additions & 11 deletions frontend/src/components/UserInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import React, { useEffect, useState } from 'react';
import api from '../services/api';

interface Props {
token: string;
}
interface Props {}

const UserInfo = ({ token }: Props) => {
const UserInfo = (_: Props) => {
const [user, setUser] = useState<object>();
const [err, setError] = useState<Error | null>();

useEffect(() => {
setError(null);

fetch('http://localhost:8000/auth/me', {
headers: {
Authorization: `Bearer ${token}`,
},
})
.then(async (resp) => {
const user = await resp.json();
api
.me()
.then(async (user) => {
setUser(user);
})
.catch((err) => setError(err));
Expand Down
38 changes: 7 additions & 31 deletions frontend/src/components/WebsocketComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,19 @@
import React, { useEffect } from 'react';
import api from '../services/api';

interface Props {
token: string;
}
interface Props {}

const WebSocketComponent = ({ token }: Props) => {
const WebSocketComponent = (_: Props) => {
useEffect(() => {
// Connect to WebSocket server
const socket = new WebSocket('ws://localhost:8000/ws');

let interval: string | number | NodeJS.Timer | undefined;

// WebSocket event listeners
socket.onopen = () => {
console.log('WebSocket connection established.');
socket.send('/login ' + token);

interval = setInterval(() => {
socket.send('ping');
}, 1000);
};

socket.onmessage = (event) => {
const connDestruct = api.connectWs((event) => {
console.log('WebSocket message received:', event.data);
// Handle the received message
};

socket.onclose = () => {
console.log('WebSocket connection closed.');
};
});

// Clean up the WebSocket connection when the component unmounts
return () => {
socket.close();
clearInterval(interval);
};
return connDestruct;
}, []);

return <div>WebSocket Component</div>;
return <span aria-description="websocket-placeholder"></span>;
};

export default WebSocketComponent;
103 changes: 103 additions & 0 deletions frontend/src/services/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
type LoginResponse = {
token: string;
};

type UserResponse = {
id: string;
name: string;
};

export type Feeds = Array<{
id: string;
url: string;
title: string;
author: string;
publish_date: string;
}>;

class API {
token: string = '';

_doRequest(
input: RequestInfo | URL,
init: RequestInit = {}
): Promise<Response> {
return fetch(`/api${input}`, {
...init,
mode: 'cors',
headers: {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json',
},
}).then(async (resp) => {
if (resp.status >= 200 && resp.status < 300) {
const json = await resp.json();
return json;
}
const err = await resp.text();
throw new Error(err);
});
}

_init() {
const authToken = window.localStorage.getItem('authToken');

if (authToken) {
this.token = authToken;
}
}

login(name: string, password: string): Promise<LoginResponse> {
return this._doRequest('/auth/login', {
method: 'POST',
body: JSON.stringify({ name, password }),
}).then((resp: any) => {
window.localStorage.setItem('authToken', resp.token);
return resp;
}) as any;
}

me(): Promise<UserResponse> {
return this._doRequest('/auth/me') as any;
}

feeds(): Promise<Feeds> {
return this._doRequest('/feeds') as any;
}

connectWs(onMessage: (event: MessageEvent<any>) => void) {
// const socket = new WebSocket('ws://localhost:8000/ws');
const socket = new WebSocket(`ws://${window.location.host}/ws`);

let interval: string | number | NodeJS.Timer | undefined;

// WebSocket event listeners
socket.onopen = () => {
console.log('WebSocket connection established.');
socket.send('/login ' + this.token);

interval = setInterval(() => {
socket.send('ping');
}, 1000);
};

socket.onmessage = onMessage;

socket.onclose = () => {
console.log('WebSocket connection closed.');
};

return () => {
socket.close();
clearInterval(interval);
};
}
}

const api = new API();

// api.login = api.login.bind(api);
// api.me = api.me.bind(api);
// api.connectWs = api.connectWs.bind(api);

export default api;
52 changes: 52 additions & 0 deletions frontend/src/setupProxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function (app) {
app
.use(
'/api/news',
createProxyMiddleware({
target: 'http://news:8001',
changeOrigin: true,
pathRewrite: {
'^/api/': '/',
},
})
)
.use(
'/api/feeds',
createProxyMiddleware({
target: 'http://news:8001',
changeOrigin: true,
pathRewrite: {
'^/api/': '/',
},
})
)
.use(
'/api/users',
createProxyMiddleware({
target: 'http://api:8000',
changeOrigin: true,
pathRewrite: {
'^/api/': '/',
},
})
)
.use(
'/api/auth',
createProxyMiddleware({
target: 'http://api:8000',
changeOrigin: true,
pathRewrite: {
'^/api/': '/',
},
})
)
.use(
'/ws',
createProxyMiddleware({
target: 'ws://api:8000',
changeOrigin: true,
})
);
};

0 comments on commit c9fe33f

Please sign in to comment.