Application Development Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
cancel
Showing results for 
Search instead for 
Did you mean: 

Task 1 - Your first service and first endpoint (July Developer Challenge - "Reverse APIs")

qmacro
Developer Advocate
Developer Advocate
6,192

This is a task in the July Developer Challenge - "Reverse APIs".

So, you've completed the warm-up task 0 and have managed to get a simple CAP server up, running and accessible from the cloud, and therefore (critically for this month's challenge) accessible by the TESTER component. Great! Now it's time to create your first API endpoint. What does that mean, and what do you have to do?

As this is your first task where you have to create both a service and an endpoint, some help and hints are given. So it's a little more reading for you, but don't worry, the subsequent task descriptions won't be as verbose.

API endpoints and services

An API endpoint very often exists in the wider context of a service, which you can think of as being a "container" for one or more endpoints. An OData service will typically offer multiple entity set resources, plus perhaps some action and function imports.

For example, the classic Northwind service offers various "collections" which normally translate into "entity sets", such as Customers, Products and Categories. And as for function and action imports, the TripPin service, for example, sports a function import called GetNearestAirport*.

*I was surprised to see that my nearest airport is apparently in Rome, but then remembered that there are only 15 airports in the dataset.

Not every service is OData, of course. But in the context of enterprise computing, it's an extremely well respected, understood and used open standard. And CAP makes it child's play to create an OData service and fill it with API endpoints.

Creating your first service

And as the idea of this challenge is to use CAP, then the first thing you'll need to do is create a simple service. You can modify the existing CatalogService that you got for free when you initialised the CAP project in Task 0. You know, the one that exposes a Books entity set, and is defined declaratively, with CDL, in srv/cat-service.cds:

using my.bookshop as my from '../db/data-model';

service CatalogService {
    @readonly entity Books as projection on my.Books;
}
 

But what we recommend is that you leave that CatalogService as it is, and create your own new service. And while you could define it in the same srv/cat-service.cds file, instead, we recommend you take advantage of the wonderfully flexible nature of the CDS compiler ... and create a (pair of) new file(s), in the srv/ directory, in which you will define your service.

Hint: As you'll see, the name of the service you'll be required to create here is basic, so, within the srv/ directory, why not use the filename basic.cds. Furthermore, the API endpoint you'll be required to create in this task is an unbound function within that service, so (as with all actions and functions) you'll need to provide an implementation, so why not use the filename basic.js for that and place that file next to (in the same directory as) basic.cds.

The requirements

Here are the specific requirements for this task.

Define a new service.

Make sure the service name is basic.

Have it be served via the OData V4 protocol (this is what all services are served via by default with CAP). But don't have it served at the standard path for an OData V4 protocol based service (which would be /odata/v4/basic), instead, have it served at the simpler path /basic.

The service, at least at this point in the challenge, needs to have a single API endpoint, specifically one that is called ping, that can be called with the HTTP GET method, takes no parameters, and returns a JSON payload that looks like this:

{
  "@odata.context": "$metadata#Edm.String",
  "value": "pong"
}
 

It is critical (to test success) that what's returned in the value property here is the JSON string "pong".

In other words, this API endpoint should be defined as an unbound function.

When the service, containing this API endpoint, is fully defined, and served via the OData V4 protocol, the metadata document should look like this:

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
  <edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml">
    <edmx:Include Alias="Common" Namespace="com.sap.vocabularies.Common.v1"/>
  </edmx:Reference>
  <edmx:Reference Uri="https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml">
    <edmx:Include Alias="Core" Namespace="Org.OData.Core.V1"/>
  </edmx:Reference>
  <edmx:DataServices>
    <Schema Namespace="basic" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityContainer Name="EntityContainer">
        <FunctionImport Name="ping" Function="basic.ping"/>
      </EntityContainer>
      <Function Name="ping" IsBound="false" IsComposable="false">
        <ReturnType Type="Edm.String"/>
      </Function>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>
 

If you've not defined (here: in srv/basic.cds) and written an implementation (here: in srv/basic.js) for a function or action before, then you need to know a couple of things.

First, know that you need to implement such functions or actions via the on event. Also, know that there are two implementation styles in CAP Node.js*, described in the Capire section How to provide custom service implementations?:

  • the ES6 class based style, extending the cds.ApplicationService class
  • the older and simpler cds.service.impl style

Note that the older style is translated automatically, behind the scenes, to the ES6 class based style anyway.

* As mentioned in the Introduction to this challenge, hints are for the Node.js flavour of CAP, but of course you're welcome to use the Java flavour if you prefer.

Once you've got your service defined, and a simple implementation ready, with an on handler for the ping event, you're ready.

It is definitely worth testing it yourself first, e.g. with curl, or Postman, or whatever tool you prefer for making HTTP calls. With your server running (on, let's say, the default local CAP server port of 4004), make a request like this:

curl -s --url 'localhost:4004/basic/ping()'
 

and the reponse should look like this:

{"@odata.context":"$metadata#Edm.String","value":"pong"}
 

Submitting your API endpoint to the TESTER

Now you're ready to submit your CANDIDATE service, with the specific API endpoint, to the TESTER!

The payload

The task identifier you need to supply in the payload of your submission is: basic-ping.

While some of you lovely folks with a more rebellious and hacker nature (I'm looking at you @ajmaradiaga and @gphadnis2000 :-)) have already kicked the tyres of the TESTER service, this should be the first time you'll be using the TESTER service.

Most of what you need to know is described in the the section titled "The Tester service, and making a test request" in the main challenge blog post, so head over to that section for a quick refresher first, then come back here.

Now, to have your freshly minted API endpoint in this task tested, you'll need to submit a JSON payload like this:

{
  "communityid": "<your-community-id>",
  "serviceurl": "<the-URL-of-your-service>",
  "task": "basic-ping"
}
 

Here's an example (don't use these values for communityid and serviceurl, they're specific to me and provided here just for illustration):

{
  "communityid": "qmacro",
  "serviceurl": "https://c0df-85-255-235-188.ngrok-free.app/basic",
  "task": "basic-ping"
}
 

Note that the value for the communityid property should be your ID on this SAP Community platform (e.g. mine is "qmacro").

Note also the value for the serviceurl property should be the absolute URL (i.e. including the scheme), of your CANDIDATE service (see ℹ️ A note on URLs and services), not the specific API endpoint.

The service URL

Of course, you need to make sure your new service and API endpoint are available to the TESTER, so supplying http://localhost:4004/basic as the value for the serviceurl property is not going to work. Make sure you either redeploy your CAP project (if you're pushing to Cloud Foundry) or still have your ngrok tunnel up and running (if you're using the ngrok tunnel approach).

Submitting the test request

You'll need to submit that JSON payload in a POST request to this endpoint:

https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/tester/testServer

and you'll need to supply a Content-Type header stating that the representation of the payload resource has a media type of application/json.

Here's a curl invocation doing exactly that, by way of example (and using my specific values shown in the illustration above):

curl \
  --data '{"communityid":"qmacro","serviceurl":"https://c0df-85-255-235-188.ngrok-free.app/basic","task":"basic-ping"}' \
  --header 'Content-Type: application/json' \
  --url 'https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/tester/testServer'
 

Types of responses

You'll see one of two types of responses- an error response, or a test result response.

Error response

If there's something wrong with your call then you'll get an error response. For example, if you haven't supplied a value for the communityid property, you'll get an HTTP 400 error response with a payload like this:

{
  "error": {
    "code": "400",
    "message": "Missing Community ID value",
    "@Common.numericSeverity": 4
  }
}
 

Similarly, if the TESTER cannot reach your endpoint, you'll get HTTP 500 error response with a payload like this:

{
  "error": {
    "code": "500",
    "message": "Error calling service endpoint",
    "@Common.numericSeverity": 4
  }
}
 

Other similar errors may occur, for example if you supply a missing or invalid task identifier or service URL.

Test result response

If the TESTER manages to reach your API endpoint successfully, it will then check that the response returned is what is required and expected. In the case of this task, the response required and expected is the static value "pong". If that is what is received, a positive test result response will be returned, and will look like this:

{"@odata.context":"$metadata#Edm.String","value":"PASS"}
 

If a different value is received, then a negative test response will be returned, and will look like this:

{"@odata.context":"$metadata#Edm.String","value":"FAIL"}
 

That's it!

Logging of test results

Each time there's a call to the TESTER by any participant, and a test result response can be issued, that test request is logged. So you can check on your progress, and the progress of your fellow participants.

The logged requests are available in an entity set served by the TESTER service. The entity set URL is https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/tester/Testlog and being an OData V4 entity set, all the normal OData system query options are available to you for digging into that information.

But that's all for now on that.

Until the next task, have fun, and if you have any questions or comments, leave them below!

42 REPLIES 42

ajmaradiaga
Developer Advocate
Developer Advocate
5,868

Pass!!!

Screen Shot 2024-07-05 at 14.49.33.png

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
5,807

🎉

mwn
Participant
5,815

I was successful with my call to the tester service, but I couldn't work out the OData filter for "result". It's an SByte, so I couldn't use $filter=result eq 'PASS'. Filtering by communityid worked ok.

qmacro
Developer Advocate
Developer Advocate
0 Kudos
5,583

Hi there @mwn 

What were you attempting to do? Get a list of test requests that succeeded, using the result property in a filter? How about: https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/tester/Testlog?$filter=result%20eq...

5,446

It looks like a typo on my part, as the query works ok now.

Alpesa1990
Participant
5,697

My submission for task 1.

Alpesa1990_0-1720216879982.png

 

spassaro
Participant

MioYasutake
Active Contributor
4,932

My submission for task 1.

MioYasutake_0-1720299114140.png

 

eshrinivasan
Developer Advocate
Developer Advocate
4,728

Task 1 submitted

eshrinivasan_0-1720356865139.png

 

MatLakaemper
Participant
4,724

MatLakaemper_0-1720358858587.png

Task 1 but not without odata/v4

MatLakaemper
Participant
4,606

MatLakaemper_0-1720365902019.png

and now without odata....   

service basic @(path: '/basic') {...

qmacro
Developer Advocate
Developer Advocate
0 Kudos
4,328

Nice one @MatLakaemper ... Yes, you'll notice that on this first API endpoint, I'm being explicitly lenient on the service path name ... things will be stricter for tasks later in this challenge 😉

YogSSohanee
Participant
4,549

My submission for Task 1 :

The metadata of the basic endpoint

YogSSohanee_2-1720382762478.png

 

GET call the endpoint 'basic' with unbound function ping:

YogSSohanee_0-1720382596938.png

 

And then the testerServer call

YogSSohanee_1-1720382648753.png

 

M-K
Active Participant

ManojKumarVarma
Explorer
4,214

Hello qmacro,

Here is my submission for Task 1.

ManojKumarVarma_0-1720418235710.pngManojKumarVarma_1-1720418265902.pngManojKumarVarma_2-1720418315420.png

ManojKumarVarma_3-1720418350595.png


Thanks,

Manoj Kumar Potharaju.

mxmw
Explorer
4,140

First time doing a developer challenge and having fun 🙂

Posting the entryPosting the entryTestlogTestlog

 

geek
Participant
4,020

As one who doesn't work on a BTP site, the local development was relatively painless but the deployment needed a lot of work. Deleting a previous database (From the June Developer Challenge), installing @Sisn/xssec and editing the cdsrc.json ("auth": "mocked"):

geek_0-1720455643027.pnggeek_1-1720455673900.png

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
3,914

Hi @geek - this is why I specifically included settings, and specifically included `{"auth":"mocked"}` in Task 0, which referred to the example simple deployment in the main blog post, detailed in the Simple Deployment to BTP section ... did that not work for you? There would have been no requirement to delete any database, nor install `@sap/xssec`, nor add the mocked setting to `.cdsrc.json`.

Ruthiel
Product and Topic Expert
Product and Topic Expert
0 Kudos
3,788

Hello @qmacro !

Here is my response from the Basic service:

Ruthiel_0-1720470202587.png

And the subsequent call of the tester service:

Ruthiel_2-1720470903624.png

 

 

gphadnis2000
Participant
3,709

Hi Dj,

i m getting below error.

 

gphadnis2000_0-1720505038720.png

Thanks,

Gaurav

qmacro
Developer Advocate
Developer Advocate
0 Kudos
3,496

Hi Gaurav, you've fallen foul of a little mechanism that is designed to discourage folks just using other folks' services to pass the tests 🙂 It looks like you've used two different SAP Community IDs:

qmacro_0-1720514567736.png

i.e. `gaurav.phadnis` and `gphadnis`, and now you're using a third ID here: `gphadnis2000`. How many SAP Community IDs do you have? I suspect only one, `gphadnis2000`. So you have caused yourself a little bit of pain here 🙂 

I would suggest, as it's still early on in the task sequence, redeploying your app with a different name other than `gphadnis-simplest-deployment`, and then also making sure you're consistently using the correct (and only) SAP Community ID value you have, in each of the calls to the TESTER, i.e. `gphadnis2000`. 

Hope that helps!

 

 

 

3,461

Thanks Dj.This helps i will try deploying new app and test same.

Yes i will use only one community id gphadnis2000.

0 Kudos
3,407

worked with new app. and community id gphadnis2000.Thanks Dj for help.

gphadnis2000_0-1720521943371.png

 

AndrewBarnard
Contributor
3,652

Good morning @qmacro - the documentation above mentions a communityid. I'm actually a little confused by whether you mean the SAP Community User Id or the SAP Community User DisplayName? Your Community User ID appears to be 53 (see https://community.sap.com/t5/user/viewprofilepage/user-id/53 ) yet you are using your displayname of qmacro. Does it matter what we use? 

0 Kudos
3,506

Hey there Andrew - great to see you here on this Developer Challenge! Sorry for the confusion. I've added an extra paragraph to "The Tester service, and making a test request" section in the main blog post:

Note the "communityid" value is your name on this SAP Community platform, for example mine is "qmacro". My numeric SAP Community user ID is 53 but the SAP Community ID we're using here is exactly the same as the one we used for the Developer Challenge on APIs last year.

Basically, the terminology I use on this platform is:

  • "SAP Community ID" for the (usually) string style name ("qmacro")
  • "SAP Community user ID" for the numeric "user-id" (53) in the profile URL

There were a couple of places in the main body of the description for Task 0 where I incorrectly used the term "SAP Community name", but I've corrected those now.

HTH, and thanks for keeping me straight!

dj

tobiasz_h
Active Participant
3,533

Hello,
My submission:

tobiasz_h_0-1720510715594.png

 

Trulov
Participant

qmacro
Developer Advocate
Developer Advocate
0 Kudos
3,292

Nice shell environment! (plus using ngrok) 🚀

sudarshan_b
Participant
3,191

My submission, PASS !! 🙂

sudarshan_b_0-1720550407900.png

 

mwn
Participant
3,070

I have a little html page which can be used to provide a quick summary of the successful calls. Because of CORS issues I couldn't easily get the test results, so I launch then in a new tab and then copy/paste them into a text block.

 

<script>
function analyze() {
	var v = document.getElementById("buffer").value;
	
	var results = JSON.parse(v);
	var tasks = [];
	var ids = [];
	for (i=0;i<results['value'].length;i++) {
		task = results['value'][i].task;
		id = results['value'][i].communityid;
		if (!tasks.includes(task)) tasks.push(task);
		if (!ids.includes(id)) ids.push(id);
	};
	tasks.sort();
	document.write("<h2>Results of challenge</h2>");
	document.write('<Table border="1"><tr><th>communityid</th>');
	for (i=0;i<tasks.length;i++) {
		document.write("<th>"+tasks[i]+"</th>");
	};
	document.write("</tr>");
	for (i=0;i<ids.length;i++) {
		id = ids[i];
		document.write("<tr><td>"+ids[i]+"</td>");
		for (j=0;j<tasks.length;j++) {
			task = tasks[j];
			found = "&nbsp;";
			for (x=0;x<results['value'].length;x++) {
				if (results['value'][x].communityid == id && results['value'][x].task == task) found = "X";
		 	};
			document.write('<td style="text-align:center">'+found+"</td>");
		};
		document.write("</tr>");
	};
	document.write("</table>");

};
</script>
Click the link <a href="https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/tester/Testlog?$filter=result%20eq%20%27PASS%27&$orderby=communityid&$select=communityid,task" target="_new" >here</a> to launch a new tab with the results, and then copy paste the contents below.
Please ensure you have the source/raw/unformatted version. 
<input id="buffer" size="200"  oninput="analyze()"/>

 

 

Create your own .html file, paste the source code, and launch the page. You can also try it at  https://michaelnicholls.github.io/results.html 

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
2,775

Fab! Thanks for sharing!

krishnan-jr
Explorer
3,025

Done!!!

krishnanjr_0-1720630609593.png

 

vineelaallamnen
Explorer
2,858

task1 

vineelaallamnen_0-1720733510222.png

 

Cmdd
Participant
2,501

Cmdd_0-1721049747522.png

Finally I found some time to play with ngrok, amazing!

C.

qmacro
Developer Advocate
Developer Advocate
0 Kudos
2,086

Great to hear! 

emiliocampo
Explorer
2,461

My Task1 Submission

emiliocampo_0-1721072030799.pngemiliocampo_1-1721072076148.png

 

0 Kudos
2,321

how to remove the odata/v4 in the deployed url to the cloud foundary. 

0 Kudos
2,180

I'm guessing that's a question ... and if so, you might want to take a look at this part of Capire: https://cap.cloud.sap/docs/node.js/cds-serve#path. Good luck! 🚀

Liyon_SV
Explorer
1,890

Late to the party 😀

Liyon_SV_0-1721708720287.png