Return data from multiple objects in a single apex method in Lightning component framework

There are primarily two approaches

1. Server side Wrapper in apex

This can be handy if the number of rows and query are within apex governor limit .Example can be lets say you return less than 50K rows and total number of queries you need is less than 100 .

You can generate a wrapper schema like above

public class AccountContactWrapper {
  @AuraEnabled
  public list<Account> accounts {get;set;}
  @AuraEnabled
  public list<contact> contacts {get;set;}
  public AccountContactWrapper(){
      this.accounts = new list<Account>();
      this.contacts = new list<contacts>();
  }
}

public class Response{
@AuraEnabled
 public static AccountContactWrapper method(){
    AccountAndContact acWrapper = new AccountAndContact();
    acWrapper.accounts = [select id, name from account limit 20000];
    acWrapper.contacts = [select id, name from contact LIMIT 20000];
    return acWrapper;
 }
}

2.Using Promise In Javascript

Javascript ES6 Promises are commonly used to call one method after other and resolve them as you wish on the client side .The benefit of this method is you can chain multiple server side calls .

Here is a pattern to help call one function after the other

accountPromise.then(
    $A.getCallback(function(result){
        // We have the account - set the attribute
        cmp.set('v.account', result);

        // return a promise to retrieve a contact
        var contAction = cmp.get("c.GetContact");
        var contParams={"accountIdStr":accId};
        contAction.setParams(contParams);
        var contPromise=self.executeAction(cmp, contAction);
        return contPromise;
    })
  )
   .then(
       $A.getCallback(function(result){
        // We have the contact - set the attribute
        cmp.set('v.contact', result);
      })
  .catch(
     $A.getCallback(function(error){
        // Something went wrong
        alert('An error occurred : ' + e.message);
    })
 ); 

Here is a neat blogpost that shows how to use them

If Promises are hard to wrap your heads around you can use below simplified callback pattern as well

// better version - separate callback functions
getAccountOwner2 : function(cmp, event, helper) {
    // cleanup
    cmp.find("contactName").set('v.value', '');
    cmp.find("accountName").set('v.value', '');
    cmp.find("ownerName").set('v.value', '');

    var getContact = cmp.get("c.getContact");
    getContact.setParams( {"contactId": cmp.get("v.recordId")} );
    getContact.setCallback(this, contactCallback);
    $A.enqueueAction(getContact);

    function contactCallback(contact) {
        contact = contact.getReturnValue();
        cmp.find("contactName").set('v.value', contact.Name);
        var getAccount = cmp.get("c.getAccount");
        getAccount.setParams( {"accountId": contact.AccountId} );
        getAccount.setCallback(this, accountCallback);
        $A.enqueueAction(getAccount);
    }
    function accountCallback(account) {
        account = account.getReturnValue();
        cmp.find("accountName").set('v.value', account.Name);
        var getUser = cmp.get("c.getUser");
        getUser.setParams( {"userId": account.OwnerId} );
        getUser.setCallback(this, userCallback);
        $A.enqueueAction(getUser);
    }
    function userCallback(user) {
        user = user.getReturnValue();
        cmp.find("ownerName").set('v.value', user.Name);
    }
}

You can simply use a map as output parameter and return for example:

return new Map<string,object> {'account': acc, 'contact': con}