Building an API, beside a (React/Firebase) web app - reactjs

I have a web app using React and Firebase Realtime Database.
The index.js file looks like the following:
import React from 'react';
import { userInfo } from 'os'
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
.....
class RouterBlock extends React.Component {
constructor(props) {
super(props);
this.state = {
logdInSatus: false
};
}
.....
render() {
.....
return (
<Router>
<Routes>
<Route exact path="/" element={<TopMenu/>} />
.....
<Route exact path="/p1" element={<Component_1/>} />
<Route exact path="/p2" element={<Component_2/>} />
<Route exact path="/p3" element={<Component_3/>} />
.....
</Routes>
</Router>
);
}
}
It all works pretty much the way I wish.
From this point, here is what I want:
An API for a customer, which is going to allow him to get data from my DB for his own use.
My app is now accessible through a URL like:
https://example.com/myWebApp
The API for the customer(s) should be used via this:
https://example.com/myAPI
and it would return JSON data from my DB that the customer can then use.
The top file for the API would probably look something like (but I am not yet sure):
const http = require("http")
import { getDatabase, ref, onValue} from "firebase/database";
import { url } from 'inspector'
import { workerData } from 'worker_threads'
const db = getDatabase();
.....
const requestListener = function (req, res) {
res.setHeader("Content-Type", "application/json")
res.writeHead(200)
res.end(`{"message": "This is a JSON response"}`)
// Here data needs to be taken from the actual DB.
}
const server = http.createServer(requestListener)
server.listen(port, host, () => {
console.log(`Server is running on http://${host}:${port}`)
})
Finally, my question is about the routing for the API url. Should it be one of the routes found in index.js or something totally separated? If this is the case (a route inside index.js), what is the proper syntax that I should use for this route? What I tried did not work.
Since all my current routes use a React component, I suppose this one needs to be different, but I don't know how to handle it. Any relevant hint will be welcome.

Related

Gatsby wildcard route

Is it not possible to route to the same component with a wildcard path?
If in React I have something like:
<Router>
<Switch>
<Route path="/path/:id" children={<Component />} />
</Switch>
</Router>
all the requests:
/path/123
/path/123/p
/path/123/p/1
will route to the same /path/123
How can I tell Gatsby to do the same?
createPage({
path: `/path/123/*`,
component,
context
})
Or what is the solution to this problem, a redirect engine of some sorts?
I think you are looking for client-only routes. Given a page (or template if it's created from gatsby-node.js) you can:
import React from "react"
import { Router } from "#reach/router"
import Layout from "../components/Layout"
import SomeComponent from "../components/SomeComponent"
const App = () => {
return (
<Layout>
<Router basepath="/app">
<SomeComponent path="/path" />
</Router>
</Layout>
)
}
export default App
Note: assuming a src/pages/app/[...].js page (File System Route API structure).
When a page loads, Reach Router looks at the path prop of each component nested under <Router />, and chooses one to render that best matches window.location (you can learn more about how routing works from the #reach/router documentation).
Alternatively, you can use an automated approach (plugin: gatsby-plugin-create-client-paths) by:
{
resolve: `gatsby-plugin-create-client-paths`,
options: { prefixes: [`/path/*`] },
},
Which will validate all routes under /path.
Or for a more customizable approach, in your gatsby-node.js:
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/path/)) {
page.matchPath = "/path/*"
// Update the page.
createPage(page)
}
}
Disclaimer: These routes will exist on the client only and will not correspond to index.html files in an app’s built assets. If you’d like site users to be able to visit client routes directly, you’ll need to set up your server to handle those routes appropriately.

Slash issues in sites which use React router

Already week I stumbled a bug which related React-router.Firstly I think this bug is only in my site.Then I find out that a giant corporation's that use a ReactJS have same issue.
If you wrote more than one slash in url and press enter you will see the (small)site(s) show header and footer.
And giant project's behaviour will be unusual.Some of components will be break some of them will not shown.
Example of this project's.
Marvel, Airbnb
So.I try to clean up my URL.
if (window.location.pathname.length > 1) {
// const url = window.location.pathname.slice(0, 1);
const url = window.location.pathname.replace(/\/\//g, "");
history.push(`${url}`);
}
I don't know why this try not work.
Codesandbox for test.If you write more than one slash in url it will show only list of component's.How to fix that?
Consider this, as a replacement to your Main.
It will fix the double or more slashes issure.
It fixed the static render for every route as only only component.
Full CodeSaneBox.
import React, { Component } from "react";
import { Switch, Route } from "react-router-dom";
import { withRouter } from "react-router-dom";
import Home from "./Home";
import Roster from "./Roster";
import Schedule from "./Schedule";
// The Main component renders one of the three provided
// Routes (provided that one matches). Both the /roster
// and /schedule routes will match any pathname that starts
// with /roster or /schedule. The / route will only match
// when the pathname is exactly the string "/"
class Main extends Component {
componentDidMount() {
this.fixDoubleSlash();
}
// Will check for double slashes and redirect to the correct path.
fixDoubleSlash = () => {
const {
location: { pathname },
history: { push }
} = this.props;
if (pathname.match(/\/{2,}/g)) {
push(pathname.replace(/\/{2,}/g, "/"));
}
};
render() {
return (
<Switch>
<Route exact path="/" component={Home} />
<Route path="/roster" component={Roster} />
<Route path="/schedule" component={Schedule} />
</Switch>
);
}
}
export default withRouter(Main);
use window.history.pushstate
if (window.location.pathname.length > 1) {
// const url = window.location.pathname.slice(0, 1);
const url = window.location.pathname.replace(/\/\//g, "");
window.history.pushState('page2', 'Title', url);
}
You need to define one component as NotFound which will render in case user tries to access any path which doesn't exist in the application.
in your Route add this
<Route component={NotFound} />
and this should be present at the end of all route otherwise you request for any URL it will go to NotFound.

Meteor, React Router 4, and Authentication

I've been scouring the inet trying to find anywhere that defines how to handle authentication in meteor and react router 4 .
Basically, I want certain routes to only be available to authenticated users. Is there any documentation on it?
Aseel
Meteor has a very well developed User Accounts system. It provides ready libraries for OAuth authentication with Twitter, Facebook, etc. as well as a basic but useful UI packages. Check Meteor's official guide here first.
For implementing routing you need to track Meteor.userId() and change route via Meteor's reactive system called Tracker. Meteor.userId() returns a userId if currently connected user is logged in, and null otherwise. I provide an example code where React Router is used for routing, below. Notice that you'll will also need the historypackage to be installed and imported while working with React Router v4.
In your client/main.js;
import { Meteor } from 'meteor/meteor';
import React from 'react';
import ReactDOM from 'react-dom';
import { Tracker } from 'meteor/tracker'
import {onAuthChange, routes} from "../imports/routes/routes";
Tracker.autorun(function(){
const authenticated = !! Meteor.userId();
onAuthChange(authenticated);
});
Meteor.startup(() => {
ReactDOM.render(routes, document.getElementById('app'));
});
And in your routes.js file;
import { Meteor } from 'meteor/meteor';
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory'
import Home from './../ui/components/Home';
import Login from './../ui/components/Login';
import NotFound from './../ui/components/NotFound';
import Signup from './../ui/components/Signup';
const history = createBrowserHistory();
const unauthenticatedPages = ['/', '/signup'];
const authenticatedPages = ['/link'];
const publicPage = function () {
if (Meteor.userId()) {
history.replace('/link');
}
};
const privatePage = function () {
if(! Meteor.userId()) {
history.replace('/');
}
};
export const routes = (
<Router history = {history}>
<Switch>
<Route exact path='/:id' component= {Login} onEnter={publicPage}/>
<Route exact path='/signup' component={Signup} onEnter={publicPage}/>
<Route exact path='/link' render={ () => <Home greet='User'/> } onEnter={privatePage} />
<Route component={NotFound}/>
</Switch>
</Router>
);
export const onAuthChange = function (authenticated) {
console.log("isAuthenticated: ", authenticated);
const path = history.location.pathname;
const isUnauthenticatedPage = unauthenticatedPages.includes(path);
const isAuthenticatedPage = authenticatedPages.includes(path);
if (authenticated && isUnauthenticatedPage) { // pages: /signup and /
console.log(`Authenticated user routed to the path /link`);
history.replace('/link');
} else if (!authenticated && isAuthenticatedPage) {
console.log(`Unauthenticated user routed to the path /`);
history.replace('/');
}
};
Here's a neat way to have public routes and authenticated routes:
https://gist.github.com/lucnat/643988451c783a8428a2811dbea3d168
public components are visible by everyone, they use the PublicLayout
authenticated components are visible by authenticated users only -
they use the AuthenticatedLayout
We could have an arbitrary number of layouts. In the example above, there are two layouts - each with it's own navbar.
I've been trying to get a more updated method using functional components.
I've tried implementing a conditional check similar to the documentation of React-router.
This was working after giving history.push to a desired route after waiting for Meteor.loginWithPassword to complete.
But refreshing the browser ended up rendering login page again.
Meteor is having an intermediate state of Meteor.loggingIn().
Handling this state in the Authentication check fixed this issue.
Feel free to give feedback.
I've created a gist with an implementation for authentication of routes in Meteor - React-router stack with functional components and hooks.
Check this gist with basic structure of the implementation.
https://gist.github.com/rinturj84/0ef61005bf3a4ca5fb665dfc5f77e3d1
Actually, the best idea to do that is to create Multiple separated routers because you can take benefits from using Meteor Reactive-var.
This is a sample :
export default function App() {
if (Meteor.user()) {
return (
<React.StrictMode>
<Global_Router Client={null} About={About} HomeNavbar={HomePageNav} HomePage_Home={HomePage_Home}
HomeFooter={Footer} Homepage_Contacts={Homepage_Contacts}/>
</React.StrictMode>
);
}
else {
return(
<RouterClient/>
);
}
Router client
export const RouterClient = () => {
return (
<Router className="container-fluid">
<Switch >
<Route exact path="/Client" >
<HomeCLient/>
</Route>
<Route path="*">
<Redirect to="/Client" />
</Route>
</Switch>
</Router>
)
};
Respectively,you can create a router for the admin too. In general, that is the most efficient way to do that.

How do I go to a specific page in React based on a URL param

As an example when entering http://localhost:3000/ui/?goto=/ui/entity/e2 in the browser I'd like to go to the Entity component e2.
This is my router:
<Route path="/ui/" component={App}>
<IndexRoute component={EntitiesPage} />
<Route component={Entity} path="entity/:id" />
<Route component={NotFound} path="*" />
</Route>
This is the App component:
import React from 'react'
const App = React.createClass( {
render() {
let gotoUrl = this.props.location.query.goto;
if (gotoUrl) {
// go here: gotoUrl;
} else {
return (
<div className="App">
{this.props.children}
</div>
)
}
}
})
export default App
this.context is empty.
this.props has:
history
location
route
routeParams (empty)
routes
UPDATE:
I've ended up using this:
import React from 'react'
import { withRouter } from 'react-router'
const App = React.createClass( {
componentWillMount() {
let gotoUrl = this.props.location.query.goto;
if (gotoUrl) {
this.props.router.replace(gotoUrl);
}
},
render() {
return (
<div className="App">
{this.props.children}
</div>
);
}
})
export default withRouter(App)
One thing that might be tripping you up is that render should have no side effects.
A "side effect" is anything that changes what's going on in your app*: updating state, making AJAX calls, or in this case, altering the page location. The render method should only read from the current state of the component, then return a value.
Because you're already using React.createClass, the best way to handle this is by adding a separate method that React handles specially: componentWillMount. I'd recommend you put your "redirect" logic here.
In order to properly change the page location, you'll need access to the browser history object which react-router manipulates. You can import this from the react-router library itself and directly call methods on it:
// At top of file
import { browserHistory } from 'react-router'
// Then, in your component:
componentWillMount() {
let gotoUrl = this.props.location.query.goto;
if (gotoUrl) {
// NOTE: this may have security implications; see below
browserHistory.push(gotoUrl);
}
}
Source: documentation.
I'd suggest that, instead of using query.goto, you instead select a parameter that can be easily validated, such as the entity ID itself (a simple regex can make sure it's valid). Otherwise, an unscrupulous user might send a link to another user and cause them to access a page that they didn't mean to.
*Note: there are stricter definitions of "side effect" out there, but this one is pretty useful for React development.
You should use browserHistory
import { browserHistory } from 'react-router';
...
if (gotoUrl) {
browserHistory.push(gotoUrl)
}
Let me know if this works

React-router, JWT Cookie, Flux, Authentification & SSR

Hello StackOverflow community !
My application background is a bit complex but I feel like my problem isn't.
So I have a debut of application which consist of a Sign Up / Log In / Private component. My routes.js contains the following
// routes.js
function requireAuth(nextState, transition) {
if (!LoginStore.isLoggedIn()) {
transition.to('/login', null, { nextPathname: nextState.location.pathname });
}
}
<Route component={require('./components/App')} >
<Route path="/" component={require('./components/Home')} />
<Route path="/login" component={require('./components/LogIn')} />
<Route path="/signup" component={require('./components/SignUp')} />
<Route path="/private" component={require('./components/Private')} onEnter={requireAuth}/>
</Route>
Log In component retrieve from an API a JWT, store it in a LoginStore component (Flux design) and in a Cookie in order to have access to the Private component without re-login later on.
The whole is working correctly as when I'm logged, I have access to the Private component and I can access it when refreshing (thanks to the cookie).
My problem comes from the fact that I am also rendering this solution on the server and if I try to access directly to /private (by directly I mean my first call on the application is /private), I am redirected to /login and then I can access to /private. I would like to fall on /private at the first call.
// server.js
var location = new Location(req.path, req.query);
Router.run(routes, location, (error, initialState, transition) => {
console.log(initialState);
if (transition.isCancelled) {
return res.redirect(302, transition.redirectInfo.pathname);
} else {
var html = React.renderToString(<Router {...initialState}/>);
res.send(renderFullPage(html));
}
});
My LoginStore should retrieve the cookie and allow the access to the Private component but he doesn't succeed because my cookie cannot be find yet.
GET http://localhost/private [HTTP/1.1 302 Moved Temporarily 30ms]
GET http://localhost/login [HTTP/1.1 200 OK 51ms]
GET http://localhost/bundle.js [HTTP/1.1 200 OK 30ms]
I feel like I should send my cookie to the Router in the server.js so the LoginStore can be set but I don't know much about how to do it and it may not be the best solution. I would really appreciate help on this problem.
Thanks you in advance.
Your solution is similar to mine. Use react-cookie for manipulating cookies and patch save/delete method on the server on each request before calling Router.run. Also make sure your routes are sync.
import Iso from 'iso';
import React from 'react';
import ReactDomServer from 'react-dom/server';
import Router from 'react-router';
import Location from 'react-router/lib/Location';
import cookie from 'react-cookie';
import routes from '../../app/routes';
import Alt from '../../app/lib/Alt';
import AltBootstrap from '../lib/AltBootstrap';
export default {render};
function render(req, res, next) {
cookie.setRawCookie(req.headers.cookie); // THIS
cookie.save = res.cookie.bind(res); // THIS
let location = new Location(req.path, req.query);
Router.run(routes, location, (error, state, transition) => {
if (error) return next(error);
if (transition.isCancelled) return res.redirect(transition.redirectInfo.pathname);
AltBootstrap.run(state, req).then(snapshot => {
Alt.bootstrap(snapshot);
let markup = ReactDomServer.renderToString(<Router {...state}/>);
let html = Iso.render(markup, Alt.flush());
res.render('index', {html});
}).catch(next);
});
}
A complete source code is available here: isomorphic-react-flux-boilerplate.

Resources