Forge API backend example help

I’m trying to create my first app where I’m following several different examples including the ones from Atlassian.

I’m able to deploy the app but it keep showing the loading animation, when looking at the development tools on the browser I get the error:

Refused to connect to ‘https://jira/rest/api/3/search’ because it violates the following Content Security Policy directive: “connect-src ‘self’ https://api.atlassian.com/metal/ingest”.

I looked online and this is due to CSP restrictions that doesn’t allow me to do API calls from the frontend. But because the Atlassian example only shows how to do it from the frontend it’s impossible for me to continue.

I looked on examples on how to do the API calls from the backend but I can’t get it to work and I’m not sure if it’s because I’m not connecting something right, or what else it could be. I believe it could be related to a bunch of 409 errors I’m getting.

Here’s my code if it helps:

backend:

import Resolver from ‘@forge/resolver’;
import api, { route } from ‘@forge/api’;

const resolver = new Resolver();

resolver.define(‘getNumber’, async ({ context }) => {
const projectKey = context.projectKey;

// Fetch issues from the project, including subtasks and epics
const response = await api.asUser().requestJira(route/rest/api/3/search?jql=project=${projectKey}&maxResults=0);
const data = await response.json();

return data.total; // Return total number of issues in current project
});

export const handler = resolver.getDefinitions();

Frontend:

import React, { useEffect, useState } from ‘react’;
import { Text } from ‘@forge/react’;
import { invoke } from ‘@forge/bridge’;

const IssueCounter = () => {
const [numero, setNumero] = useState(null); // State to hold the number of issues
const [error, setError] = useState(null); // State to hold errors

useEffect(() => {
const loadIssues = async () => {
try {
const numero = await invoke(‘getNumber’);
setNumero(numero); // Update state with the number of issues
} catch (error) {
console.error(‘Error fetching issues:’, error);
setError(‘There was an issue fetching the data. Please try again.’);
}
};

loadIssues(); // Call the function inside useEffect to load issues on component mount

}, ); // Empty dependency array ensures it runs once on mount

return (
<>
{error ? (
{error}
) : (
Number of issues: {numero !== null ? numero : ‘Loading…’}
)}
</>
);
};

export default IssueCounter;

The help from this community would be appreciated as I wasn’t able to find any other thread about how to use the API between backend and frontend.

Hi @DavidFabila! Welcome to the Atlassian developer community :slight_smile:

Regarding your first issue (hitting CSP restrictions): While it is true that we put controls around where your app’s front-end can communicate, it should be straight forward to communicate back with Jira itself. The error message “refused to connect to ‘https://jira/rest/api/3/search” seems like there could be something going wrong with the construction of the URL. “jira” is obviously not a vaild domain name, and it should be replaced with the URL of your Jira instance. Were you actually seeing “jira” in the literal error message, or did you edit it to obscure your URL in this community post? As a quick note, you need to use the requestJira function from the @forge/bridge library to make API calls from your app’s front-end back to the Atlassian API; you can’t just manually craft an XHR or fetch (this is covered in the tutorial, so I assume you are familiar with this, but wanted to double-check).

With your changed code to call the API from the backend, everything looks roughly OK. You’ll need to connect your front-end to the resolver by declaring it in your app manifest - did you specify your resolver for the front-end there?

What errors or behaviour are you seeing with this updated code? The HTTP 409 errors could be a red herring as I think some of those always get fired from within Jira/Confluence itself at the moment.

I’m not a react expert, but I wonder if your useEffect hook is correctly declared? You define loadIssues within the hook, but then try to call it from outside the hook? I know JavaScript allows this because it has weird rules around closures, but this looks a bit odd to me to reference a function from outside its defined scope.

1 Like

Hey Joe thank you so much for your quick feedback. It’s strange I’m getting the Refuse to connect to https://jira/rest/api/3/search’ error, because I’m using “route” in the code :

const response = await api.asUser().requestJira(route/rest/api/3/search?jql=project=${projectKey}&maxResults=0 );

so is there something I’m doing wrong while using route/ ? I expected that to resolve any URL to pull directly from the project where the App is running.

I’ve been trying for a while and I’m still not able to even make any example work

Hello @DavidFabila

Can you confirm that, after declaring the resolver in the manifest, the error still refers to jira instead of the URL of your Jira instance {yourdomain}.atlassian.net

PS. In the request, you passed the parameter &maxResults=0, which is kinda pointless, as you’re essentially saying “Return a maximum of nothing”. Just leave the maxResults parameter out of your request and you’ll get the default of 50.

It’s strange I’m getting the Refuse to connect to https://jira/rest/api/3/search’ error, because I’m using “route” in the code

The route is designed to handle this and insert the correct Jira host name into the constructed URL. The fact that this isn’t working makes me suspect there is a bug in your code somewhere.

This code:

const response = await api.asUser().requestJira(route`/rest/api/3/search?jql=project=${projectKey}&maxResults=0` );

is how you would call the Jira API from the Forge backend (ie. a Lambda function). If you are using this code correctly via a resolver function, you should not see a " Refuse to connect to https://jira/rest/api/3/search’" error in your browser because this code does not run within your browser.

Are you able to share a more complete sample of your code, and a more detailed example of the error you are seeing?

1 Like