Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 

Index



  1. App Init

  2. Setting up proxy: easy way

  3. Setting up the backend

  4. React component: fetching and posting

  5. Testing

  6. Deployment

  7. Conclusion


Hi everyone!


Have you tried developing with CAP and React and you have had trouble calling your APIs? Fear no more!


In this tutorial we are going to learn how to proxy our api requests to our backend in local development. We are going to setup the proxy in the react app and we are going to create a simple endpoint in our CAP application that we are going to be able to call.


In production it is the approuter who is in charge of taking care of the routing using destinations, but in local development we are not using the approuter. We are going to use reacts default dev server. As you know this server already comes with a ton of features for local development such as our beloved hot reloading - automatic re-rendering on save.



App Init


To start off we will be using a small script my team and I created to make your life easier when building full stack applications in Cloud Foundry called cf-create-app. This will install CAP with an approuter, react application, html5 repo service, add Hana and some tweaks so deployment is as straightforward as possible. Simply run



npx cf-create-app <NAME OF THE APP>

After installation is complete you can simply go into the generated folder and run these commands to get the application started. This will start the react app and execute cds watch to get your CAP app running.



cd <NAME OF THE APP>

npm run dev

That’s it!


A new tab will open up in your browser at port 3000.



Setting up proxy: easy way


To setup the proxy simply go into the app folder and open up the package.json. You will only have to add the following:


PROJECT/app/package.json



{

“proxy”: “http://localhost:4004:,

}

Easy right?


To test it out and see it working we are going to modify a few things. Let’s start with the backend service.



Setting up the backend


There are default files in db and srv folders. We are ONLY going to modify srv/book-service.cds. Delete everything in the file and just paste:


// PROJECT/srv/book-service.cds



using {bookshop as bookshop} from '../db/schema';

service BookshopService @(path : '/browse') {
entity Books as projection on bookshop.Books
}

That’s all. Now we will be able to use CRUD methods with our entity Books. In our simple example we are just going to GET books and POST books.



React component: fetching and posting


We are going to create a small react component that will let us create new books for our “library” and fetch books we have in stock. The endpoint we are calling to fetch the books is “browse/Books”, for two main reasons:




  •  Locally the proxy is going to redirect the request to our backend, so browse/Books is going to be redirected to http://localhost:4004/browse/Books

  • When deployed to the cloud the approuter is going to take care of re-routing the request to the server. More on this later


// PROJECT/app/src/Books.js



import React from 'react';

const booksEndpoint = ‘/browse/Books’;

function renderBooks(books) {
return books.map((book, index) => {
return (
<div key={book.title + index}>
<h6>{book.title}</h6>
<ul style={{ marginLeft: '20px' }}>
<li>Price: {book.price}$</li>
<li>Stock: {book.stock}</li>
</ul>
</div>
);
});
}

async function fetchBooks() {
const response = await fetch(booksEndpoint);
const books = await response.json();
return books.value;
}

async function submitBook({ title, price, stock }) {
const response = await fetch(booksEndpoint, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json;IEEE754Compatible=true',
},
body: JSON.stringify({
ID: Math.floor(Math.random() * 9999),
descr: '',
price: String(price),
stock: Number(stock),
title,
}),
});

const newBook = await response.json();
return newBook;
}

function Books() {
const [books, setBooks] = React.useState(null);
const [title, setTitle] = React.useState('');
const [price, setPrice] = React.useState(19.99);
const [stock, setStock] = React.useState(400);

React.useEffect(() => {
async function updateBooks() {
setBooks(await fetchBooks());
}

updateBooks();
}, []);

return (
<div>
<form
style={{ textAlign: 'center' }}
onSubmit={async (e) => {
e.preventDefault();
const newBook = await submitBook({ title, price, stock });
if (newBook.error) {
return console.log(newBook.error.message);
} else {
setTitle('');
setPrice(19.99);
setStock(400);
setBooks(await fetchBooks());
}
}}
>
<label>Title </label>
<input
value={title}
placeholder='Book title'
onChange={(e) => setTitle(e.target.value)}
required
></input>

<label> Price </label>
<input
type='number'
value={price}
onChange={(e) => setPrice(e.target.value)}
></input>

<label> Stock </label>
<input
type='number'
value={stock}
onChange={(e) => setStock(e.target.value)}
/>
<br />
<br />
<button type='submit'>Upload Book</button>
</form>
{books && renderBooks(books)}
</div>
);
}

export default Books;

We import this file into our App.js file in the src folder, modify your file to look as such:


// PROJECT/app/src/App.js



function App() {
return (
<div style={{ margin: '20px 50px' }}>
<Books />
</div>
);
}

export default App;

Testing


If you now go to the root of your project and run $ npm run dev your server should start on port 4004 and your react application on port 3000. You can now add a title of a book, change the price and stock, on submit you should see the book appear on screen.


There is a slight problem right now. If you check your approuter destinations in PROJECT/approuter/xs-app.json you will see the destination will only work for source /api/ , but our endpoint is pointing to /browse/Books. There are two options in my opinion to solve this issue:




  1. Create a destination for each service endpoint - the service we are consuming is the one we created before under PROJECT/srv/book-service.cds, and as you can see this service is exposed on path /browse. So one option would be to create a new destination for each service you create. Kind of messy in my opinion.

  2. Have the React app serve the endpoint differently for production and for development. Here is a small example of how you could approach this. Go to Books component and modify booksEndpoint line with the following:


// PROJECT/app/src/Books.js




const isDev = process.env.NODE_ENV === 'development';
const booksEndpoint = `${isDev ? '' : '/api'}/browse/Books`;

Now when you deploy the application to Cloud Foundry the url will be /api/browse/Books and the approuter will redirect the request to the backend. And while in development the route will remain /browse/Books.



Deployment


Deploy as always, first build the mta.yaml like such:



mbt build

Or if you don’t have it installed globally yet you can run



npx mbt build

And deploy using cf cli - you have to be logged in your Cloud Foundry account



cf deploy mta_archives/<generated file in previous step>

Once deployment has finished you should see the URL of the approuter in the terminal, just copy and paste it in your browser and your done!



Conclusion


Today we’ve learnt how to proxy api requests to our backend. There are more ways of solving this problem, but this would be a good starting point for anyone new to the topic. Hope you enjoyed it and please do not hesitate to ask any questions.


Link to repository containing this project


https://github.com/Turutupa/cf-html5-repo-api-proxing-react


 

Link to cf-create-app repository


https://github.com/Turutupa/cf-create-app


 

Happy hacking! 🙂

1 Comment